Structures Arborescentes – Implémentations Nathalie Junior Bouquet avril 2013 1 Arbres binaires Définition Un arbre binaire est soit vide, soit de la forme B = <o, B1 , B2 >, où B1 et B2 sont des arbres binaires disjoints et ’o’ est un nœud appelé racine. e u c u q l q o e n Figure 1 – Arbre binaire 1.1 Le type algébrique abstrait sorte ArbreBinaire utilise Nœud, Élément opérations arbre-vide <–, –, –> contenu racine g d : : : : : : → ArbreBinaire Nœud × ArbreBinaire × ArbreBinaire → ArbreBinaire Nœud → Élément ArbreBinaire → Nœud ArbreBinaire → ArbreBinaire ArbreBinaire → ArbreBinaire préconditions racine(B1 ) est-défini-ssi B1 ̸= arbre-vide g(B1 ) est-défini-ssi B1 ̸= arbre-vide d (B1 ) est-défini-ssi B1 ̸= arbre-vide axiomes racine(<o, B1 , B2 >) = o g(<o, B1 , B2 >) = B1 d (<o, B1 , B2 >) = B2 avec B1 , B2 : ArbreBinaire o : Nœud 1 Algorithmique Types abstraits : Les arbres – avril 2013 Info-Sup Epita L’opération contenu permet d’associer à chaque Nœud de l’arbre une information de type Élément. Un arbre dont les nœuds contiennent des éléments est dit arbre étiqueté. On notera, abusivement, <r, G, D> l’arbre dont la racine contient l’élément r. r G D Figure 2 – Arbre binaire : une structure récursive 1.2 Représentation dynamique La représentation la plus naturelle reproduit la structure récursive (voir figure 2). On utilise les pointeurs pour chaîner entre eux les nœuds. A chaque nœud on associe deux pointeurs, vers les deux sous-arbres gauche et droit. L’arbre non vide est représenté par un pointeur sur le nœud racine. Il a la valeur nul s’il est vide. Nous utiliserons la plupart du temps des arbres étiquetés. Un champ supplémentaire cle contiendra l’information contenue dans le nœud. 1.2.1 Le type types /* déclaration du type t_element*/ t_arbreBinaire = ↑ t_noeudBinaire t_noeudBinaire = enregistrement t_element cle t_arbreBinaire fg, fd fin enregistrement t_noeudBinaire B e c u NUL u q l NUL NUL q e o n NUL NUL NUL NUL NUL NUL NUL NUL Figure 3 – Représentation dynamique de l’arbre de la figure 1 2 Algorithmique Types abstraits : Les arbres – avril 2013 1.2.2 1.3 Info-Sup Epita Implémentation des opérations Type abstrait : ArbreBinaire Implémentation : type t_arbreBinaire B : ArbreBinaire B : t_arbreBinaire arbre-vide nul contenu(racine(B)) B↑.cle g(B) B↑.fg d(B) B↑.fd Représentation statique On peut simuler la représentation précédente à l’aide de tableaux 1 : les pointeurs sont alors remplacés par des entiers indiquant les positions des nœuds dans le tableau. L’arbre est représenté par le tableau, ainsi qu’un entier indiquant la position de la racine. 1.3.1 La numérotation hiérarchique On peut utiliser un simple vecteur pour représenter un arbre binaire. Il suffit de stocker chaque nœud à la position correspondant à sa numérotation hiérarchique. 1 f 2 3 i p 4 5 n 8 u 6 r 9 _ 7 a t 10 a Figure 4 – Arbre binaire parfait + numérotation hiérarchique constantes nbMAxNoeuds = ... types /* déclaration du type t_element*/ t_AB_Hierarchique = nbMAxNoeuds variables t_AB_hierarchique t_element B L’arbre de la figure 4 sera représenté par le tableau suivant : 1 f 2 p 3 i 4 n 5 r 6 a 1. Selon le même principe que pour les ”fausses listes chaînées“ 3 7 t 8 u 9 _ 10 a Algorithmique Types abstraits : Les arbres – avril 2013 Info-Sup Epita L’utilisation de cette représentation est intéressante sur un arbre parfait (ou complet, comme celui de la figure 8), car on peut limiter la taille du vecteur à celle de l’arbre. Par contre, sur un arbre quelconque, la place occupée n’est plus optimisée. Représentation de l’arbre de la figure 1 : 1 e 2 c 3 u 4 u 5 l 6 q 7 8 9 q 10 e 11 12 o 13 n La place occupée sera d’autant moins optimisée que la hauteur de l’arbre sera élevée. Dans le cas d’un arbre dégénéré (ou filiforme) à n nœuds, l’espace occupé peut alors atteindre 2n − 1 ! f i l i f e Figure 6 – Peigne droit Figure 5 – Arbre filiforme Représentation de l’arbre filiforme de la figure 5 : 1 f 2 3 i ... ... 7 l ... ... 14 i ... ... 29 f ... ... 15 g ... ... 30 i 31 n 58 e Représentation du peigne droit de la figure 6 : 1 p 1.3.2 2 d 3 e ... ... 6 r 7 i ... ... 14 o ... ... 62 t 63 e Utilisation ◦ La racine est en position 1 dans le vecteur. ◦ Si i est la position du nœud actuel alors : ◃ son fils gauche se trouve à la position 2i ◃ son fils droit se trouve à la position 2i + 1 ◃ son père (sauf pour la racine de l’arbre), se trouve à la position i div 2. En pratique, il faut avoir une valeur particulière (∅) pour remplacer la racine de l’arbre vide. Sauf si l’arbre est parfait : la taille de l’arbre suffit ! 4 Algorithmique Types abstraits : Les arbres – avril 2013 1.4 1.4.1 Info-Sup Epita Parcours d’un arbre binaire Parcours en profondeur Le parcours en profondeur main gauche consiste à descendre dans l’arbre à gauche le plus loin possible. Lorsque l’on ne peut plus aller à gauche, on descend à droite. Lorsqu’on ne peut plus descendre, on remonte d’un niveau : si on vient de la gauche, on descend à droite, sinon on remonte encore. . . La manière la plus simple d’envisager ce parcours est récursive. Il revient tout simplement à parcourir le sous-arbre gauche, puis à parcourir le sous-arbre droit ! r (1) (3) (2) G (1) : préfixe D (2) : infixe (3) : suffixe Figure 7 – Parcours en profondeur d’un arbre binaire Lors du parcours en profondeur, chaque nœud est rencontré trois fois : (1) avant de descendre sur le sous arbre-gauche : ordre préfixe, (2) en remontant de la gauche, avant de descendre à droite : ordre infixe (ou symétrique), (3) en remontant de la droite : ordre suffixe (ou postfixe). Exemples : les trois ordres de traitement des nœuds d’un arbre induits lors d’un parcours en profondeur. ◦ L’ordre préfixe sur l’arbre de la figure 8 donnera : lui_est_complet ◦ L’ordre infixe sur l’arbre de la figure 4 donnera : un_parf ait ◦ L’ordre suffixe sur l’arbre de la figure 1 donnera : quelconque l u c s i _ e t o _ m l p e Figure 8 – Arbre binaire complet 5 t Algorithmique Types abstraits : Les arbres – avril 2013 Info-Sup Epita L’algorithme : Avec la représentation statique : Avec la représentation dynamique : ↓ algorithme procedure parcours_prof parametres locaux t_arbreBinaire B debut si B[i] = ∅ alors /* ∅ = "vide" */ sinon prof_rec (B, 2*i) prof_rec (B, 2*i+1) fin si fin algorithme procedure prof_rec debut si B = nul alors /* terminaison */ sinon /* traitement préfixe */ parcours_prof (B↑.fg) /* traitement infixe */ parcours_prof (B↑.fd) /* traitement suffixe */ fin si fin algorithme procedure parcours_prof 1.4.2 algorithme procedure prof_rec parametres locaux t_AB_hierarchique B entier i → algorithme procedure parcours_prof parametres locaux t_AB_hierarchique B debut prof_rec(B, 1) fin algorithme parcours_prof Parcours en largeur Le parcours en largeur ou par niveaux consiste à visiter les nœuds niveaux par niveaux (en général de gauche à droite) : c’est l’ordre hiérarchique. L’algorithme classique utilise une file qui sera utilisée comme suit : – on enfile la racine de l’arbre – tant que la file n’est pas vide : – on défile le premier élément de la file – on enfile chacun de ses fils (s’ils ne sont pas vides), d’abord le gauche puis le droit. L’algorithme : Avec la représentation dynamique algorithme procedure parcours_largeur parametres locaux t_arbreBinaire B variables t_file f debut si B <> nul alors f ← enfiler (B, file_vide()) faire B ← defiler (f) /* traitement B↑.cle */ si B↑.fg <> nul alors f ← enfiler (B↑.fg, f) fin si si B↑.fd <> nul alors f ← enfiler (B↑.fd, f) fin si tant que non est_vide (f) fin si fin algorithme procedure parcours_largeur Pour la représentation statique, on peut envisager une implémentation sans file, en effectuant un parcours séquentiel du vecteur (en passant les éventuelles valeurs ∅). Mais cela n’est réellement intéressant que lorsque l’arbre est complet ! Sinon, il suffit d’utiliser une file d’entiers, qui contiendra les numéros en ordre hiérarchique des nœuds. 6