CPGE OUJDA Les arbres binaires SPE Introduction La structure d'arbre est l'une des plus importantes et des plus spécifiques de l'informatique. Exemples d’utilisation : organisation des fichiers dans les systèmes d'exploitation internet : DNS (Dynamic Naming System) mécanismes internes des compilateurs/interpreteurs Il existe plusieurs types d'arbre : binaires, planaires, ... Une propriété intrinsèque de la structure d’arbre est la récursivité. Un arbre binaire (ou n-aire) est une structure de données de type hiérarchique. Les éléments constituant un arbre ont pour nom : racine, nœuds et feuilles. Le nœud initial est nommé racine. Les éléments terminaux sont des feuilles. Dans un arbre binaire, chaque élément possède au plus deux éléments fils au niveau inférieur. Un arbre qui possède deux voire plus de sous éléments est appelé "père", les éléments inférieurs étant appelés des "fils". La hauteur (ou profondeur) d’un noeud est la longueur du chemin qui le lie `a la racine. Arbres binaires Un arbre binaire est un arbre tel que les nœuds ont au plus deux fils (gauche et droit). Les arbres binaires (AB) forment une structure de données qui peut ˆêtre définie récursivement de la manière suivante : un arbre binaire est – soit vide, – soit compos´e d’une racine portant une étiquette (clé) et d’une paire d’arbres binaires, appelés fils gauche et droit. Exemple 1: A B D Arbres binaires de recherche C E F G Un arbre binaire de recherche est un arbre binaire qui possède la propriétéfondamentale suivante: – tous les nœuds du sous-arbre de gauche d’un nœud de l’arbre ont une valeur inférieure ou égale `a la sienne. – tous les nœuds du sous-arbre de droite d’un nœud de l’arbre ont une valeur supérieure ou égale `a la sienne. 15 10 12 8 2 19 11 22 14 20 25 Parcours d'un arbre binaire : Le parcours le plus simple à programmer est le parcours dit en profondeur d’abord. Son principe est simple : pour parcourir un arbre non vide a, on parcourt récursivement son sous-arbre gauche, puis son sous-arbre droit, la racine de l’arbre pouvant être traitée au début, entre les deux parcours ou à la fin. Dans le premier cas, on dit que les nœuds sont traités dans un ordre préfixe, dans le second cas, dans un ordre infixe et dans le troisième cas, selon un ordre postfixe. Parcours préfixe, (NGD) :_ tout Nœud est suivi des nœuds de son sous-arbre Gauche puis des nœuds de son sous-arbre Droit 12 Affichage_prefixe(AB a) si NON est_vide(a) Afficher val(a) Affichage_prefixe(fils_gauche(a)) Affichage_prefixe(fils_droit(a)) 1 91 7 67 82 61 12 1 91 67 7 82 61 Parcours infixe, ou symétrique (GND) :_ tout Nœud est précédé des nœuds de son sous-arbre Gauche et suivi des nœuds de son sous-arbre Droit Affichage_infixe(AB a) si NON est_vide(a) Affichage_infixe(fils_gauche(a)) Afficher val(a) Affichage_infixe(fils_droit(a)) 91 1 67 12 7 61 82 Parcours suffixe, post-fixe (GDN) :_ tout Nœud est précédé des nœuds de son sous-arbre Gauche et des nœuds de son sous-arbre Droit Affichage_postfixe(AB a) si NON est_vide(a) Affichage_postfixe(fils_gauche(a)) Affichage_postfixe(fils_droit(a)) Afficher val(a) 91 67 1 61 82 7 12 Parcours en largeur. Visite les nœuds niveau par niveau depuis la racine: Peut-être décrit facilement en utilisant une File. affichageLargeur(a): F : File #File FIFO Enfiler(a,F) #enfiler la racine a dans la file F tantque non vide(F): n=Défiler(F) si non vide(n): Afficher(val(n)) Enfiler(filsGauche(n),F)#enfiler fils gauche de n dans f Enfiler(filsDroit(n),F) #enfiler fils droit de n dans F Impl´ ementation en Python par des listes On impl´emente les arbres binaires non vides par des listes de trois ´el´ements : [valeur,[fils gauche],[fils droit]]. Ou bien,[[fils gauche], valeur, [fils droit]]. Exemples : A= [20,[],[]] #A est un arbre qui contient un seul nœud (20) A= [20, [ 5, [], [] ] , [ 3, [], [] ] ] A=[20, [ 5, [10,[],[] ], [14,[],[]] ] , [ 3, [], [] ] ] A=[20, [ 5, [10,[],[] ], [14,[],[]] ] , [ 3, [22,[],[]], [29,[],[]] ] ] 20 EXERCICES Exercice1 En utilisant la forme ,[[fils gauche], valeur, [fils droit]]. Ecrire en python les fonctions suivantes ; 1) def inserer(r, x): qui insert un noeud x dans notre arbre binaire de recherche r 2) def prefixer(r):, def infixer(r):, et def postfixer(r): selon les algorithmes précédents 3) def liste_vers_arbre(liste): """création d'un arbre à partir d'une liste""" Exercice2 : En utilisant la forme [valeur, [fils gauche], [fils droit]]Ecrire les fonctions suivantes : 1) Fonction qui détermine si un arbre a est vide ou non. 2) Fonction qui retourne la valeur de la racine d’un arbre (étiquette). 3) Fonction qui retourne le fils gauche de l’arbre a . 4) Fonction qui retourne le fils Droit de l’arbre a . 5) Fonction qui fait le Parcourt d’ un arbre en largeur 6) Déterminer si un nœud est une feuille 7) Hauteur d’un arbre 8) Calculer le nombre de nœuds d’un arbre 9) Calculer le nombre de feuilles dans un arbre 10) Fonction de recherche dans un arbre binaire quelconque Solutions Exercice1 : def inserer(r, x): """insertion d'un noeud dans notre arbre binaire""" if r == []: r.append([]) r.append(x) r.append([]) elif x < r[1]: inserer(r[0], x) else: inserer(r[2], x) def prefixer(r): """parcours préfixé""" if r == []: return None else: print r[1], prefixer(r[0]) prefixer(r[2]) def infixer(r): """parcours infixé""" if r == []: return None else: infixer(r[0]) print r[1], infixer(r[2]) def postfixer(r): """parcours postfixé""" if r == []: return None else: postfixer(r[0]) postfixer(r[2]) print r[1], def liste_vers_arbre(liste): """création d'un arbre à partir d'une liste""" arbre = [] for i in liste: inserer(arbre, i) return arbre import random liste = [] for i in range(50): liste.append(i) random.shuffle(liste) a=liste_vers_arbre(liste) prefixer(a) infixer(a) postfixer(a) Solutions Exercice2 : 1: defvide(a): return a==[] 2: def val(a): if a!=[]: return a[0] else: return None 3:def filsDroit(a): ifnot vide(a): return a[2] else: return [] 5: def parcoursLargeur(a): F=[] #File FIFO F.append(a) #enfiler la racine a dans la file F while not vide(F): n=F[0] ; F=F[1:] #défiler F (récupérer la tête) print(val(n),end=' ') if not vide(filsGauche(n))): F.append(filsGauche(n)) #enfiler le fils gauche de n dans F if not vide(filsDroit(n))): F.append(filsDroit(n)) #enfiler le fils droit de n dans F print() 6: def estFeuille(a): if vide(a) : return False return a[1]==[] and a[2]==[] 7: def hauteur(a): if vide(a)or estFeuille(a): return 0 else: return 1 + max(hauteur(filsGauche(a)),hauteur(filsDroit(a))) 8: def nombreNoeuds(a): if vide(a): return 0 else: return 1+ nombreNoeuds(filsGauche(a)) + nombreNoeuds(filsDroit(a)) 9: def nombreFeuilles(a): if vide(a): return 0 elif estFeuille(a): return 1 else: return nombreFeuilles(filsGauche(a))+ nombreFeuilles(filsDroit(a)) 10 : Def existe(a, x): if vide(a): return False else: if val(a) ==x: return True else: return existe(filsGauche(a),x) or existe(filsDroit(a), x)