TD d’algorithmique Arbres 1 / Exemples Arbre généalogique source : http://derniersvalois.canalblog.com/archives/2007/04/30/5615361.html Arbre binaire de recherche : Alphabet Morse source : http://pauillac.inria.fr/~maranget/X/TC/X.97/TD-6/enonce.html Arbre lexicographique E C I E I L E L C L L I E I I L I L C E L C E C E L C I I C C E E C Arbre de jeu source : http://www.tony-lambert.fr/these/these.html Arbre binaire de codage : Compactage de Huffmann source : http://replicateur.free.fr/site%20tpe%202004/pages/huffman.html Arbre d'expression algébrique 2 / Définitions Arbre : structure ordonnée de "noeuds" où chaque noeud a 0, 1 ou plusieurs successeur et chaque noeud a un et un seul prédécesseur, sauf un seul noeud qui n'en a aucun racine A ancêtres de R père de H B C D profondeur 1 fils de D E K F G L M H N O I J P profondeur 2 Q feuilles R descendants de C S Tout noeud peut être considéré comme un arbre dont il est la racine Exemple dans l'arbre précédent si on identifie : le noeud G à l'arbre le noeud E à l'arbre N U et le noeud D à l'arbre E G M T K O D H L R S I J P Q T alors l'arbre peut s'écrire plus simplement A B E C F G D U Arbre binaire : Chaque noeud a au plus deux fils Remarque : Si la hauteur est n, l'arbre a au plus 1 2 4 ... 2n 2n 1 1 noeuds 1 11 12 fils droit de 12 fils gauche de 12 111 112 121 122 1222 n'a pas de fils 121 n'a qu'un fils gauche 1111 1112 1121 11211 11212 1122 11221 1221 1211 11222 12211 1222 12212 Chaque noeud est caractérisé par une une valeur (appelée clé, ou étiquette) et ses fils éventuels (c'est à dire le sous-arbre de gauche et le sous-arbre de droite.. Exemple dans l'arbre binaire précédent, le noeud 112 est caractérisé par 112 , 1121 , 1122 , le noeud 121 est caractérisé par 121 , 1211 , et le noeud 1222 par 1222 , arbre vide Un arbre binaire est donc : soit l'arbre vide soit la donnée d'une clé et de 2 arbres binaires arbre vide , arbre vide 3 / Implémentation en Python d'un arbre binaire Dans la plupart des langages, l'implémentation des arbres se fait à l'aide de 'pointeurs' , c'est-à-dire de variables qui contiennent l'adresse mémoire d'autres variables. En langage Python, on utilisera une implémentation utilisant des listes : Définition récursive : Un arbre binaire est soit la liste vide soit une liste composée d'une clé et de 2 arbres binaires exemples arbre0=[] arbre1=[1,[],[]] arbre2=[2,arbre0,arbre0] arbre3=[3,arbre2,arbre1] arbre4=[4,arbre3,arbre1] >>> [ 4 , [3, [2,[],[]],[1,[],[]]], [1,[],[]]] 4 / Primitives Quelle que soit l'implémentation et le langage utilisé, on doit pouvoir disposer pour un arbre binaire des fonctions élémentaires suivantes appelées primitives : créer un arbre vide, ou à partir d'une clé et de 2 arbres déterminer si un arbre est vide déterminer le fils gauche d'un noeud (c'est à dire en fait le sous-arbre gauche de l'arbre dont la racine est le noeud considéré . Il peut être vide) déterminer le fils droit d'un noeud déterminer la clé d'un noeud changer la clé, le fils gauche ou le fils droit de la racine d'un arbre Exercice. Coder en python les fonctions suivantes (on suppose que A est un arbre). Les tester def def def def arbre_vide(): arbre(val,A,B): est_vide(A): fils_gauche(A): # # # # retourne retourne retourne retourne un arbre vide un arbre de clé 'val' et de fils A et B vrai si l'arbre est vide, faux sinon le fils gauche de la racine de l'arbre # (si A non vide) def fils_droit(A): # retourne le fils droit de la racine de l'arbre def cle(A): # retourne la clé de la racine de l'arbre def change_cle(A,valeur): # retourne l'arbre A dans lequel la clé de # la racine est remplacée par 'val' def change_fils_gauche(A,B): # retourne l'arbre A dans lequel le fils gauche def change_fils_droit(A,B): # de la racine est remplacé par l'arbre 'B' # idem Désormais on n'utilisera plus de liste pour travailler sur des arbres, mais seulement les primitives ci-dessus. Exercice. Coder en python les fonctions suivantes (on suppose que A et B sont des arbres) def est_fils_de(A,B): # vrai si A est un fils de B, faux sinon def est_pere_de(A,B): # vrai si A est le père de B, faux sinon def est_ancetre_de(A,B): # vrai si A est un ancetre de B, faux sinon (faire une fonction récursive) def hauteur(A): # donne la hauteur de l'arbre (faire une fonction récursive) def est_cle_de(x,A): # vrai si x est la clé d’un nœud de A, faux sinon (faire une fonction récursive) Exercice Coder en python une fonction affichant un arbre comme sur l'exemple suivant affiche(arbre4) 4 On pourra par exemple définir une fonction récursive |--- 3 en ajoutant un paramètre qui donne le nombre |--- 2 d'espaces à écrire avant la clé de la racine |--- 1 def affiche(A,nbespaces): |--- 1 ... 5 / Parcours en profondeur On commence à la racine, et on parcourt l'arbre de haut en bas, puis vers la droite Dans cet exemple, on obtient la liste de clés : (compléter) 1 , 11 , 111 , 1111 , 1112 , 112 , 1121 , 1 11 12 111 1111 112 1112 11211 121 1122 1121 11212 11221 122 1221 1211 11222 1222 12212 12211 Algorithme récursif : Pour parcourir l'arbre, on note la clé de la racine, on parcourt l'arbre fils_gauche, puis l'arbre fils-droit. Exercice. Coder en python la fonction récursive parcours_en_profondeur(A) qui retourne la liste des clés, dans l'ordre du parcours. Remarque : comme dit plus haut, on ne travaille pas sur des listes pour les arbres. On n'utilise que les primitives. Exercice. Coder en python une fonction qui retourne la liste des (clés des ) descendants de B dans l'arbre A. Exercice Coder en python qui retourne la liste des ancêtres de B dans l'arbre A. Parcours en largeur On parcourt tous les noeuds situés à une même profondeur, de gauche à droite, avant de passer aux noeuds de la profondeur suivante. A début B Algorithme Créer une liste contenant l'arbre (la racine) Tant que cette liste n'est pas vide, enlever le premier élément, noter sa clé, et ajouter à la liste son fils gauche et son fils droit (s'ils existent) Dans l'exemple ci-contre, on obtient la liste de clés C D G E H I F J K L A, B, C , D, E , F , G, H , I , J , K , L, M , R Exercice Coder en python la fonction parcours_en_largeur(A) qui retourne la liste des clés. M R fin 6 / Arbres binaires de recherche Exercice Soit une liste triée. Créer un arbre binaire dont les clés sont les termes de la liste, et tel que la clé de chaque noeud soit supérieure aux clés du fils gauche et de tous ses descendants, et inférieure aux clésdu fils droit et de tous ses descendants. Par exemple, pour la liste [ a , c , e , f , h , k , l , r , s , t , v , w , z ] on veut obtenir l'arbre suivant : r clés inférieures à ' f ' f c a t k e w Clés supérieures à ' f ' h l s z v Un arbre vérifiant la propriété précédente est appelé arbre binaire de recherche. Pour chercher une clé dans un tel arbre, on procède récursivement : Si l'arbre est vide, la clé ne s'y trouve pas. Sinon on regarde si la clé se trouve à la racine Sinon on regarde si la clé est avant la clé de la racine. Dans ce cas on la recherche dans le fils gauche, sinon on recherche dans le fils droit. Exercice. Coder en python la fonction récursive recherche( cle, ABR ) qui retourne vrai si la clé est dans l'arbre binaire de recherche ABR, faux dans le cas contraire. Exercice. Étudier la complexité d'une telle recherche : nombre de comparaisons au maximum pour trouver une clé dans un arbre binaire de recherche. Montrer que cette complexité dépend principalement de la hauteur de l'arbre Il est donc préférable que l'arbre soit "équilibré", c'est-à-dire que tous les sous-arbres aient "à peu près" la même hauteur. Suggestion d'exercices : Dans un arbre binaire de recherche : Insérer une clé supprimer une clé recherche la chaine de longueur maximale reliant la racine aux différentes feuilles améliorer l'"équilibre" de l'arbre Proposer une démarche à structure d'arbre pour rechercher un mot dans un dictionnaire en moins de 20 comparaisons ( le Larousse contient environ 60 000 entrées)