Chapitre 4 Structure d'arbre 4.1 Dénition Un arbre est un graphe connexe sans cycle, non orienté et qui commence par une racine r, la structure d'arbre induit une partition sur l'ensemble des n÷uds de l'arbre. En d'autres termes, un arbre est un ensemble de n÷uds (sommets), un n÷ud contient une information, il est relié avec un n÷ud père et peut avoir de 0n ls, remarquant qu'il y'a un n÷ud qui n'a pas de père on l'appel la racine de l'arbre. taille d’arbre= 15 nombre de feuille= 9 hauteur = 4 nombre de noeuds internes=5 racine 47 noeud interne 11 5 14 20 1 14 38 36 51 39 49 Figure 4.1 un exemple d'arbre avec ses caractéristiques 1 feuille Vocabulaire et propriétés de la structure arbre Un arbre (tree ) de n sommet a n − 1 arêtes Il existe qu'un seul arête entre deux n÷uds les reliant. La taille d'un arbre est le nombre de n÷ud (number of node ) de l'arbre. Un n÷ud peut avoir 0 à plusieurs ls, la racine n'a pas de père. Une feuille (leaves ) est un n÷ud qui n'a pas de successeurs (ls), les autres n÷uds sont appelés n÷uds internes La hauteur (height) d'un arbre est la longueur du plus long chemin à partir de la racine Un n÷ud n est dit accessible à partir d'un n÷ud n0 s'il existe un chemin allant de n0 à n dans l'arbre. Dans le cas général, un arbre est appelé arbre à n-aires (gure ; mais ils existent autres types peuvent être considérés comme des cas spéciaux, dans ce cours on s'intéresse par les arbres binaires (Binary trees, gure 4.1). 4.2 Arbre binaire Un arbre binaire est une structure de données récursive qui peut être représenter sous forme hiérarchique formé par des n÷uds, le n÷ud initial étant appelé racine. Une des propriétés des arbres binaires est que chaque n÷ud possède au plus deux éléments ls au niveau inférieur, habituellement appelés ls gauche et ls droit. Du point de vue de ces ls, le n÷ud dont ils sont issus au niveau supérieur est appelé père. Au niveau le plus élevé il y a donc un n÷ud racine. Au niveau directement inférieur, il y a au plus deux n÷uds ls. Un n÷ud n'ayant aucun ls est appelé feuille. Le nombre de niveaux total, autrement dit la distance entre la feuille la plus éloignée et la racine, est appelé hauteur de l'arbre. Le niveau d'un n÷ud est appelé profondeur de n÷ud. On peut utiliser les arbres binaires en tant que arbre binaire de recherche. 4.2.1 Implémentation d'un arbre binaire L'implémentation la plus simple d'un arbre binaire est illustrée dans le listing type a r b r e = ^abr abr = e n r e g i s t r e m e n t noeud : e n t i e r ; 2 gauche , d r o i t e : a r b r e Listing 4.1 Impléméntation d'un arbre binaire 4.2.2 Parcours d'arbre Le parcours d'arbre est une façon d'ordonner les n÷uds d'un arbre an de les parcourir. le résultat du parcours d'un arbre est une liste d'éléments qui n'est pas souvent explicite . On distingue deux types essentiels de parcours : en profondeur ou en largeur. Dans les parcours en profondeur on distingue à nouveau le parcours préxe(pré-ordre), le parcours inxe (in-ordre) et le parcours suxe(post-ordre), dans la suite on utilise l'arbre de la gure 4.2 . 1 2 4 3 5 6 0 7 9 Figure 4.2 Parcours d'arbre binaire 4.2.3 Parcours d'arbre en profondeur Ce parcours, tient toujours à visiter le n÷ud le plus éloigné de la racine que possible, à la condition qu'il soit le ls d'un n÷ud déjà visité. À l'opposé des parcours en profondeur sur les graphes, il n'est pas nécessaire de connaître les n÷uds déjà visités, car un arbre ne contient pas de cycles. Les parcours préxe, inxe et postxe sont des cas particuliers de ce type de parcours. Dans ce parcours, sa version itérative doit conserver une liste des éléments en attente de traitement. La liste utilisée dans ce parcours doit avoir une structure de pile (de type LIFO, Last In, First Out ). 3 Parcours préxe Le parcours préxe ache les valeurs de l'arbre en pré-ordre. C'est à dire on visite la racine ainsi que le ls gauche puis le ls droit, il faut bien noter que cette procédure est récursive. Procédure parcours − pref ix( A : arbre) Début ecrire(A ∧ .noeud) parcours − pref ix(A Si (non(arbre − vide(A ↑ .gauche))) Alors parcours − pref ix(A ↑ .gauche) ; .gauche) ; ↑ Fin Si Si (non(arbre − vide(A ↑ .droite))) Alors parcours − pref ix(A ↑ .droite) ; Fin Si Fin Algorithme 1 Parcours préxe d'un arbre binaire 1 1 2 2 4 6 3 3 5 4 6 5 8 7 Chaine de parcours préfixe= 1, 2, 3, 5, 6, 7, 4, 0, 9 Figure 4.3 Parcours préxe 4 7 9 0 Parcours inxe Le parcours inxe (in ordre) explore le sous-arbre gauche, la racine et le sous-arbre droit Procédure parcours − inf ix( A : arbre) Début Si (non(arbre − vide(A ↑ .gauche))) Alors parcours − inf ix(A ↑ .gauche) ; Fin Si ecrire(A ↑ .noeud) Si (non(arbre − vide(A ↑ .droite))) parcours − inf ix(A ↑ .droite) ; Alors Fin Si Fin Algorithme 2 Parcours inxe d'un arbre binaire 1 2 1 3 6 4 5 2 3 7 5 0 4 6 8 7 Chaine de parcours infixe= 3, 2, 6, 5, 7, 1, 4, 9, 0 Figure 4.4 Parcours inxe 5 9 Parcours postxe Le parcours en postxe permet de visiter le sous-arbre gauche, le sousarbre droit et puis la racine. Procédure parcours − postf ix( A : arbre) Début Si (non(arbre − vide(A ↑ .gauche))) Alors parcours − postf ix(A ↑ .gauche) ; Fin Si Si (non(arbre − vide(A ↑ .droite))) Alors parcours − postf ix(A ↑ .droite) ; Fin Si Fin ecrire(A ↑ .noeud) Algorithme 3 Parcours postxe d'un arbre binaire 1 2 1 3 6 4 5 2 3 7 5 0 4 6 8 7 9 Chaine de parcours infixe= 3, 2, 6, 5, 7, 1, 4, 9, 0 Figure 4.5 Parcours postxe 4.2.4 Parcours d'arbre en largeur Dans le parcours en largeur on parcourt l'arbre niveau par niveau. Les n÷uds de niveau 0 sont sont d'abord parcourus puis les n÷uds de niveau 1 6 et ainsi de suite. Dans chaque niveau, les n÷uds sont parcourus de la gauche vers la droite. Au Contraire du parcours en profondeur, ce parcours essaie toujours de visiter le n÷ud le plus proche de la racine qui n'a pas déjà été visité. En suivant ce parcours, on va d'abord visiter la racine, puis les n÷uds à la profondeur 1, puis 2, etc. D'où le nom parcours en largeur. La structure de le d'attente (de type FIFO, First in, rst out ), ce qui induit que l'ordre de sortie n'est pas le même (i.e. on permute gauche et droite dans le traitement). P ro c ed u re P a r c o u r s L a r g e u r (A: a r b r e ) { // i n i t i a l i s a t i o n de l a f i l e e n f i l e r ( Racine (A) , f ) Tant que ( f <> n i l ) { // l a f i l e n ' e s t pas v i d e noeud = d e f i l e r ( f ) e c r i r e ( noeud ) S i ( noeud ^ . gauche ) <> n i ) A l o r s e n f i l e r ( noeud ^ . gauche , f ) S i ( noeud ^ . d r o i t e ) <> n i l ) A l o r s e n f i l e r ( noeud ^ . d r o i t e , f ) } } Listing 4.2 parcours en largeur 7 Procédure W ideSearch( A : arbre) Dbut init(F ) f = F ileV ide; enf iler(A ↑ .noeud, f ); Tant que (not(f ile − vide(f ))) n = def iler(f ); ecrire(n); Si (A ↑ .gauche 6= nil)) Alors enf iler(A ↑ .gauche, f ) faire Fin Si Si (A ↑ .droite 6= nil)) Alors enf iler(A ↑ .droite, f ) Fin Fin Si Fait Algorithme 4 Parcours en largeur d'un arbre binaire 4.2.5 Calcul de la hauteur d'un arbre On peut dénir cette fonction récursivement comme suit : un arbre vide est de hauteur 0 un arbre non vide a pour hauteur 1 + la hauteur maximale entre le sous arbre gauche et le sous arbre droit. A partir de cette dénition l'algorithme 5 illustre le calcul de la hauteur d'arbre binaire. 8 Fonction Height( A : arbre) : entier Dbut Si (arbre − vide(A)) Height ← 0 ; Alors Sinon Height ← 1 + max(Height(A.gauche), Height(A.droite)) ; Fin Si Fin Algorithme 5 Calcul de hauteur d'un arbre 4.2.6 Calcul de taille d'un arbre Comme la hauteur de l'arbre, le calcul de la taille de l'arbre c'est à dire le nombre de n÷ud se calcule récursivement selon la dénition suivante : La taille d'un arbre vide est 0 La taille d'un arbre qui n'est pas vide est égale à 1 + la taille du sous arbre gauche + taille du sous arbre droit. L'algorithme 6 montre comment la taille de l'arbre est calculé. Fonction N BN ode( A : arbre) : entier Dbut Si (arbre − vide(A)) N BN ode ← 0 ; Alors Sinon N BN ode ← 1 + N BN ode(A.gauche) + N BN ode(A.droite) ; Fin Si Fin Algorithme 6 Calcul de la taille d'un arbre 4.2.7 Calcul de nombre de feuilles dans un arbre Pour calculer le nombre de feuilles on prend la dénition récursive : Un arbre vide ne contient aucune feuille. 9 Le nombre de feuille dans un arbre non vide est déni de la façon suivante : Dans le cas où le n÷ud est une feuille alors on renvoie 1, si c'est un n÷ud interne alors le nombre de feuille est la somme du nombre de feuille de chacun de ses deux ls. Ainsi l'algorithme 7 calcul le nombre de feuille dans l'arbre avec f euille(A) vérié si le n÷ud est une feuille. Fonction nbleaves( A : arbre) : entier Dbut Si (arbre − vide(A)) nbleaves ← 0 ; Alors Sinon Si (f euille(A)) Alors nbleaves ← 1 ; Sinon nbleaves ← nbleaves(A.gauche) + nbleaves(A.droite) ; Fin Si Fin Si Fin Algorithme 7 Calcul de la taille d'un arbre(nombre de n÷ud) 4.2.8 Calcul de nombre de n÷uds internes Un n÷ud interne est déni par un n÷ud qui n'est pas racine et n'est pas feuille, l'algorithme 8 traduit la dénition récursive du calcul de n÷uds internes suivante : un arbre vide n'a pas de n÷ud interne. si le n÷ud en cours est une feuille alors renvoyer 0 si le n÷ud a au moins un ls, renvoyer 1 plus la somme des n÷uds interne des ls. 10 Fonction internalN ode( A : arbre) : entier Début Si (arbre − vide(A)) Alors internalN ode ← 0 ; Sinon Si (f euille(A)) Alors internalN ode ← 0 ; Sinon internalN ode ← 1 + internalN ode(A.gauche) + internalN ode(A.droite) ; Fin Si Fin Si Fin Algorithme 8 Calcul de nombre de n÷uds internes d'un arbre 4.3 Arbre n-aires Un arbre de n-aires est une généralisation des arbres binaires, à la diérence de nombres de ls qu'on peut trouver jusqu'à n ls alors que l'arbre binaire peut avoir au plus deux ls. 4.3.1 Implémentation L'implémentation d'un arbre à n-aires peut avoir deux situations diérentes, la première quand le nombre de ls est connu la structure de données devient un n÷ud qui pointe vers un tableau de n ls, la deuxième situation quand le nombre de ls est inconnu ou on veut optimiser l'espace mémoire, l'implémentation de cette dernière est que chaque n÷ud pointe vers le premiers ls et vers son frère, comme le montre la gure 4.6 4.4 Arbre binaire de recherche Un arbre binaire de recherche (ou ABR) est un arbre binaire qui permet de représenter un ensemble de valeurs disposant d'une relation d'ordre sur ces valeurs de tel façon que les n÷uds du sous arbre gauche sont inférieurs 11 Arbre de n-aires /n est inconnu Vue de l’implementation Figure 4.6 Implémentation d'arbre n-aires aux n÷uds du sous arbre droit. Les opérations de base sur les arbres binaires de recherche sont l'insertion, la suppression, et la recherche d'une valeur. La gure 4.7 illustre un exemple d'un arbre binaire de recherche, pour avoir une chaine ordonnée on doit acher les éléments de l'arbre par un parcours inxe, ainsi la localisation de la plus petite valeur ou le minimum ici c'est 5 dans l'arbre est obtenu par une descente vers les ls à gauche jusqu'au dernier qui est le minimum, et c'est applicable aussi pour le maximum qui est situé dans l'extrême droit dans l'exemple de la gure : 51. 33 15 47 10 5 20 38 18 36 51 39 49 Figure 4.7 Exemple d'un ABR 4.4.1 Insertion d'un élément Pour insérer une valeur on commence par une recherche : on cherche la valeur du n÷ud à insérer ; lorsqu'on arrive à une feuille, on ajoute le n÷ud comme ls de la feuille en comparant sa valeur à celle de la feuille : si elle est inférieure, le nouveau n÷ud sera à gauche ; sinon il sera à droite. Le code d'insertion illustré dans l'algorithme 9 s'exécute récursivement, avec une remarque qu'il faut retenir, l'élément inséré est toujours une feuille. 12 Procédure InsertionABR( valeur : entier,var Début Si (arbre − vide(A)) Alors A: arbre) cre − arbre(A, nil, nil) ; Sinon Si (valeur>A.noeud) Alors InsertionABR(valeur, A.droite Sinon Si (valeur<A.noeud) Alors InsertionABR(valeur, A.gauche Fin Si Fin Si Fin Si Fin Algorithme 9 Insertion dans un ABR Dans l'exemple de la gure 4.7 on veut insérer la valeur 4 dans l'arbre, après la recherche de son emplacement le mieux adéquat, puisque 4 est une valeur inférieur à la racine 33 encerclé en blue on l'insère dans le sous arbre gauche ainsi de suite jusu'a trouver la feuille où insérer la valeur 4 (voir gure 4.8). 33 47 15 20 10 5 18 38 36 51 49 insertion de 4 4 Figure 4.8 Insertion de la valeur 4 dans l'ABR 13 4.4.2 Recherche d'un élément Pour rechercher une valeur dans un ABR, il faut parcourir une branche à partir de la racine, en descendant chaque fois sur le ls gauche ou sur le ls droit suivant que la valeur du n÷ud encourt est plus grande ou plus petite que la valeur cherchée. On arrête la recherche si la valeur est trouvée ou si on atteint une feuille donc l'élément n'existe pas, l'algorithme 10 montre comment la recherche est faite dans un ABR de façon récursive. Fonction rechercher( valeur : entier, A : arbre) : booléen Début Si (arbre − vide(A)) Alors recherche ←− f aux ; Sinon Si (valeur = A.noeud) Alors recherche ←− vrai ; Sinon Si (valeur>A.noeud) Alors rechercher(valeur, A.droite) Sinon rechercher(valeur, A.gauche) Fin Si Fin Si Fin Si Fin Algorithme 10 Recherche d'une valeur dans un ABR En utilisant l'exemple de la gure 4.7 pour chercher la valeur 39, en comparant la valeur recherchée par le n÷ud en court à partir de la racine, on trouve 39 alors que 17 est normalement située à gauche de 18 qui n'est pas les cas (voir gure 4.11. 4.4.3 Successeur d'un n÷ud Le successeur d'un n÷ud p dans un arbre A si il existe, est le n÷ud de n qui a la valeur la plus petite des valeurs qui gurent dans l'arbre A et qui est plus grand que la valeur de p. Si p possède un ls droit, son successeur est le n÷ud d'extrême gauche dans le sous-arbre droit. Si p n'a pas de ls droit 14 33 15 47 10 20 5 51 38 18 36 49 39 33 47 15 10 5 38 20 36 18 51 39 49 Figure 4.9 Recherche de la valeur 39 et 17 dans l'ABR 15 alors le successeur de p est le premier de ses ascendants tel que p apparait dans son sous-arbre gauche. Si cet ascendant n'existe pas c'est que p a la plus grande valeur dans l'arbre. La gure 4.10, montre le successeur de la valeur 33 qui est le n÷ud de l'extrême gauche du sous arbre droit c'est à dire 36, alors que le n÷ud 20 qui n'a pas de ls droit doit chercher dans ses ancêtre que 20 apparait dans son sous arbre( pour 33, 20 est le plus proche). 33 15 47 10 5 38 20 18 36 51 39 49 Figure 4.10 Exemple de successeur de 20 et de 33 4.4.4 Suppression d'un élément Pour cette opération on peut avoir trois situation selon le nombre de ls : Supprimer une feuille : dans ce cas on supprime le ls directement puisqu'il n'a pas de successeur Supprimer un n÷ud avec un seul ls : Supprimer un n÷ud avec deux ls : Soit un n÷ud qu'on veut supprimer appelé N (le n÷ud de valeur 33 rempli en rouge dans la gure ??). Pour eectuer la suppression de N il faut l'échanger avec son successeur (arrondi en blue) le plus proche (le n÷ud le plus à gauche du sous-arbre droit, le n÷ud de valeur 36) ou son plus proche prédécesseur (le n÷ud le plus à droite du sous-arbre gauche, le n÷ud de valeur 30). Cela permet de garder l'ordre de l'arbre binaire de recherche. Puis on applique à nouveau la procédure de suppression à N , qui est maintenant une feuille ou un n÷ud avec un seul ls. 16 33 15 47 10 20 18 38 30 51 39 36 49 36 15 47 10 20 18 38 30 49 39 Figure 4.11 Suppression de la valeur 33, 5 et 51 dans l'ABR 17 4.4.5 Algorithme d'équilibrage d'ABR 4.5 Application des arbres 18