710 likes | 979 Vues
Arbres. Un arbre est soit un arbre atomique (une feuille ), soit un n oeud et une suite de sous-arbres. racine. noeud interne. père. fils droit. fils gauche. feuilles. L 'ensemble des n oeuds est constitué des n œuds internes et des feuilles.
E N D
Arbres Un arbre est soit un arbre atomique (une feuille), soit un noeud et une suite de sous-arbres. racine noeud interne père fils droit fils gauche feuilles L'ensemble des noeuds est constitué des nœuds internes et des feuilles
L’intérêt de cette organisation est de laisser à l’utilisateur le soin de regrouper les fichiers à saconvenance tout en maintenant une structurehiérarchique.
Représentation symbolique des arbres Arbre vide Arbre singleton Arbre quelconque Arbre binaire CECI N'EST PAS UN ARBRE deux pères
Par définition un arbre est une structure de données constituée d’un nœudappelé racine et desous-arbres fils de la racine. C’est donc une définition récursive. racine Un nœud peut contenir une ou plusieurs valeurs, et on parlera alors d'arbres étiquetés et de la valeur (ou des valeurs) d'un nœud.
Les caractéristiques d’un arbre sont : • La taille de l’arbre est le nombre total de nœuds. • La hauteur ou niveau d’un nœud x est le nombre de liens sur • l’unique cheminallant de la racine à x, notée h(x). • La hauteur ou profondeur de l’arbre A, • h(A) = max { h(x) } • x racine x
hauteur d'un nœud dans un arbre de racine r: si x = r h(x) = 0 sinon h(x) =1 + h(père(x)) La profondeur d'un nœud est la longueur du chemin qui le joint à la racine. La racine est de hauteur 0, ses fils de hauteur 1 et les k autres nœuds de hauteur supérieure à 1. La longueur de cheminement de l’arbre A,LC(A) = h(x) x
Exemple de mesures profondeur # noeuds # feuilles 0 1 0 1 2 0 2 3 2 3 1 0 4 2 1 5 1 0 6 2 2 LC(A) = h(x) x h = 6 Taille = 12 Nbrf = 5
Arbres binaires a d b g b f c d a c e g e arbre équilibré f Ladifférence entre la hauteur du sous-arbre gauche et la hauteur du sous-arbre droit est d'au plusune unité. La recherche d'une cléd'un côté sera plus lente qu'une recherche de l'autre côté.
Arbre binaire typedef struct cellule { int data; struct noeud *fils_gauche; struct noeud *fils_droit; } nœud;
• Un Arbre est un pointeur sur un nœud, la racine de l’arbre : • typedef nœud *arbre; • • Un Arbre vide est un pointeur NULL : arbre = NULL; • • Nouveau : il faut faire une allocation mémoire et placer l’étiquette. • En cas d’erreur d’allocation le pointeur renvoyé est NULL (l’arbre est vide) : • arbre nouveau_binaire(int elt, arbre racine) { • racine = (nœud*) malloc(sizeof (nœud)); • if (racine != NULL)){ • racinedata = elt; • racine fils_gauche= NULL; • racine fils_droit=NULL; • } • return(racine); • } elt NULL NULL
Il faut relier un noeud à un ou plusieurs sous arbres. • arbre cons_binaire(arbre racine, arbre s_arb_g, arbre s_arb_d) • { • racine fils_gauche = s_arb_g; • racine fils_droit = s_arb_d; • return(racine); • }
Hauteur int HauteurArbre(arbre A) { if(A non vide) return (1 + Max(HauteurArbre(Ag), HauteurArbre(Ad)) ); else return 0; } Complexité : O(n) Nombre noeud int NbrNoeud(arbre A) { if(A non vide) return (1 + NbrNoeud(Ag) + NbrNoeud(Ad) ); else return 0; }
Max noeud int MaxNoeud(arbre A) { if(A non vide) return Max(data, MaxNoeud(Ag), MaxNoeud(Ad)); else return 0; } Min noeud int MinNoeud(arbre A) { if(A non vide) return Min(data, MinNoeud (Ag), MinNoeud (Ad)) ; else return 0; }
Un arbre binaire de recherche est un arbre binaire tel que pour tout nœud x , les nœuds de son sous arbre-gauche s’ils en existent ont des valeurs inférieures ou égales à celle de x, et les nœuds de son sous arbre-droit des valeurs strictement supérieures. 24 X Ce que l’on traduit par g(A) racine(A) < d(A). 10 37 <=X >X Arbre binaire de recherche ABR Utilisation importante en Info pour la localisation, +, -, tri …
r Ag Ad <= r > r Un arbre binaire est soit vide, noté Ø, soit de la forme < r, Ag, Ad> où r est la racine et où Ag et Ad sont des arbres binaires.
Tout sous-arbre d’un ABR est un ABR 24 29 16 Ag Ad 35 8 20 27 1 15 Exemple d’arbre binaire de recherche
racine test si arbre binaire et de recherche bool TestABR(arbre T){ if(T non vide){ if( TestABR(Td) est un ABR && TestABR(Tg) est un ABR) { if((Td pas vide) && (Tddata <= Tdata)) return (probleme) else { if((Tg pas vide) && (Tgdata > Tdata)) return (probleme) else return (OK) } } else return (???) } else return (???) } x Tg Td <= x > x Complexité : O(2n+1) * O(1) = O(n)
12 Parcours en profondeur d'un arbre binaire de recherche 9 23 -2 10 22 78 -77 9 On considère l’opération de parcoursd’un arbre binaire qui consiste à examiner systématiquement dans un certain ordre tous les nœuds de l’arbres pour effectuer un traitement de données.
Parcours préfixe 12 Lister Père Prefixe(Fils_G) Prefixe(Fils_autres) 9 23 void Prefixe(arbre racine){ if (! vide(racine)){ printf(“%d\t”,racinedata); Prefixe(racinefils_gauche); Prefixe(racinefils_droit); } } -2 10 22 78 -77 9 Le parcours en profondeur à gauche consiste à partir de la racine et à tourner autour de l’arbre en allant toujours le plus à gauche possible. Parcours préfixe :12, 9, -2, -77, 9, 10, 23, 22, 78
I II Parcours infixe III 12 Infixe(Fils_G) Lister Père Infixe(Fils_autres) 9 23 -2 10 22 78 void infixe(arbre racine){ if (! vide(racine)){ infixe(racinefils_gauche); printf(“%d\t”,racinedata); infixe(racinefils_droit); } } -77 9 infixe :-77, -2, 9, 9, 10, 12, 22, 23, 78 SI ABR Le parcours infixe affiche les éléments dans l’ordre croissant.
I II III Parcours suffixe ou Postfixe 12 9 23 -2 10 22 78 -77 9 void Postfixe(arbre racine){ if (! vide(racine)){ Postfixe(racinefils_gauche); Postfixe(racinefils_droit); printf(“%d\t”,racinedata); } } Parcours Postfixe : -77,9, -2, 10, 9, 22, 78, 23, 12
Exemple: Parcours 3 Parcours préfixe : 3, 1, 0, 2, 5, 4, 6 5 1 4 6 0 2 Infixe(Fils_G) Lister Père Infixe(Fils_autres) Parcours infixe : 0, 1, 2, 3, 4, 5, 6 Posfixe(Fils_G) Posfixe(Fils_autres) Lister Père Parcours postfixe : 0, 2, 1, 4, 6, 5, 3
Exemple: Parcours 1 Parcours préfixe : 2 3 4 5 6 7 Infixe(Fils_G) Lister Père Infixe(Fils_autres) Parcours infixe : Posfixe(Fils_G) Posfixe(Fils_autres) Lister Père Parcours postfixe :
24 29 16 35 3 20 27 1 5 Recherche d’un élément Recherche dichotomoque rechercher : valeur xdans arbre == Rech(x,arbre) booléen On compare l’élément à la valeur de la racine : - si le sous-arbre sélectionné est vide, l’élément est absent échec rechercher ( x , ) = faux - si égalité succèsx = r rechercher (x ,<r , g , d > ) = vraie - si la valeur est plus petite, on recommence récursivement dans le sous-arbre gauche ; et réciproquement si la valeur est plus grande dans le sous-arbre droit x < r rechercher (x , < r , g , d > ) = rechercher (x , g ) x > r rechercher (x , < r , g , d > ) = rechercher (x , d ) Complexité : La complexité au pire est en O ( hauteur de l’arbre ).
Soit à rechercher 20 dans l'arbre suivant 24 20 est plus petit que 24 20 est plus grand que 16 29 16 20 est trouvé 35 3 20 27 1 5
e Adjonction d’un élément aux feuilles L’adjonction aux feuilles d’un élément se réalise en deux étapes : - étape de recherche pour savoir où insérer le nouvel élément ; - adjonction elle-même. arbre ajout_feuille (arbre A, int e ) { if (A== ) return < e , , > elseif ( e racine(A) ) return < racine(A) , ajout_feuille( g(A) , e ) , d(A) > else return < racine(A) ,g(A) , ajout_feuille( d(A) , e ) > } La complexité d’une adjonction est O ( h(A) ).
x NULL NULL arbre insert(arbre T, int x) { if(T vide) { //sommet vide T = (struct nœud *) malloc(sizeof(struct nœud)) Tdata = x Tdroit = NULL Tgauche = NULL }else{ //sommet non vide if(Tdata == x) //ne rien faire !! else { if(x < Tdata) //inserer ds arbre gauche Tg = insert(Tg, x) else //inserer ds arbre droit Td = insert(Td, x) } } return T }
La complexité d’un ajout est O ( h(A) ). Alors que l’insertion dans un tableaunécessite dedéterminer sa place, en parcourant le tableau depuis le début (k comparaisons) puis de décaler les (n-k)éléments successeurs pour ménager une place. Donc une complexité en O(n) avec n le nombred’éléments du tableau. ABR réduire la complexité entemps mais pas la complexité de la programmation !!!
immédiate Suppression d’un élément arbre supprimer (arbre , valeur) arbre • recherche de l’élément à supprimer • suppression qui dépend de la place de l’élément soit on remplace le nœud à supprimer par le plus grand élément de son sous-arbre gauche, soit on le remplace par le plus petit élément de son sous-arbre droit. noeud avec deux fils nœud avec un seul fils nœud sans fils remplace le nœud par son fils
arbre suppression ( arbre A , int e ) { if (A== ) // recherche de l’élément à supprimer return erreur; if ( e < racine(A) ) return < racine(A), suppression( g(A) , e ) , d(A) ); elseif ( e > racine(A) ) return < racine(A), g(A) , suppression( d(A) , e ) ); else { // on l’a trouver donc suppression ifest_feuille(A) return ( ); else if(g(A)== ) return d(A); else if(d(A)== ) return g(A); else { // on ajoute l’élément le plus à droite du sous-arbre gauche retourner < max_noeud(g(A)) , retire_max(g(A)), d(A) > } } }
int max_noeud ( arbre A){ // retourne le plus grand élément de l’arbre A, le plus à droite if ( d(A) == ) return racine(A); elsereturn max_noeud(d(A)) ; } // retourne l’arbre privé de son plus grand élément arbre retire_max ( arbre A ){ if ( d(A) == )return g(A); elsereturn < racine(A) , g(A) , retire_ max(d(A)) >; } La complexité est O ( h(A) ).
15 14 2 13 8 5 7 La structure de tas • La structure de tas est un arbre vérifiant les deux propriétés suivantes: • L’arbre est un arbre binaire parfait • La valeur de tout nœud est >= à celle de ses descendants Arbres binaires complets Un arbre binaire est complet si tous les nœuds qui ne sont pas des feuilles ont 2 fils.
tas 15 14 2 13 8 Arbres binaires parfaits, ordre hiérarchique Un arbre binaire est parfait si toutes ses feuilles sont situées sur les deux derniers niveaux, l’avant dernier étant complet, et les feuilles du dernier sont le plus à gauche possible. Attention ! un arbre binaire parfait n’est pas forcément complet.
24 16 23 8 10 22 7 1 5 4 • Tas = arbre binaire parfait partiellement ordonné • ¨ arbre parfait: • – toutes les feuilles sont sur les deux derniers niveaux, • – l'avant dernier niveau est complet • – les feuilles du dernier niveau sont le plus à gauche possible ¨ partiellement ordonné: – tout nœud est plus grand que ses deux fils
Tests !! Arbres binaires complets Un arbre binaire est complet si tous les nœuds qui ne sont pas des feuilles ont 2 fils. 15 14 2 Arbre binaire complet ? 8 5 7
Tests !! Arbres binaires complets Un arbre binaire est complet si tous les nœuds qui ne sont pas des feuilles ont 2 fils. 15 9 14 2 13 Arbre binaire complet ? 8 5 7
15 14 2 13 8 5 7 Tests !! Arbres binaires parfaits, ordre hiérarchique Un arbre binaire est parfait si toutes ses feuilles sont situées sur les deux derniers niveaux, l’avant dernier étant complet, et les feuilles du dernier sont le plus à gauche possible. Attention ! un arbre binaire parfait n’est pas forcément complet. Arbre binaire parfait? Arbre binaire complet ?
La structure de tas Racine = + gd valeur Profondeur ou Nbr nœuds Niveau Max 0 20 = 1 1 21 = 2 2 22 = 4 h=3 au Max 23 = 8 24 16 23 8 10 22 7 1 5 Exemple de tas = 2h+1 - 1 N <=
Index 1 2 3 2*3+1=7 2*2=4 5 6 8 9 10 Index 2*Index 2*Index + 1 Profondeur ou Nbr nœuds Niveau Max 0 20 = 1 1 21 = 2 2 22 = 4 3 au Max 23 = 8 24 16 23 8 10 22 7 1 5 4 Exemple de tas Nœud x en i, son père est en i/2 Nœud x en i, son fils gauche en 2*i Nœud x en i, son fils droit en 2*i+1
Relation entre un tas et tableau Index 1 int pere(i){ return (i/2); } int gauche(i){ return (2 * i); } int droit(i){ return (2 * i + 1); } 2 3 2*3+1=7 2*2=4 5 6 8 9 10 24 16 23 8 10 22 7 1 5 4 i 1 2 3 4 5 6 7 8 9 10 24 16 23 8 10 22 7 1 5 4 tab[i]
24 16 23 8 22 22 7 22 10 1 5 4 Opérations sur les tas Insertion d’un élément dans un tas 24 16 23 8 10 22 7 1 5 4 Nouveau nœud insérer le plus à gauche possible sur le niveau de profondeur le plus élevée. tjr arbre binaire complet mais pas forcement un tas.
1 3 2 4 5 6 7 8 9 10 11 24 24 22 16 23 23 16 8 22 22 7 8 22 7 10 10 1 5 4 1 5 4 tant que ( y racine ) et ( y > père(y) ) faire échanger y et père(y) Compléxité : O(h) avec h : hauteur du tas Or la hauteur d’un tas de taille n = log2n l’insertion requiert un temps O(logn) Insertion de n éléments O(nlogn)
void ajouter (int tab[], int ntas, int val ) { // ajoute l’élément x au tasde ntas éléments int i; ntas ++ ; i =ntas ; tab[i] = val ; while ( ( i > 1 ) && ( tab[i/2] < tab[i] ) ){ Echanger ( tab[i], tab[i / 2] ); i = i/2 ; } } VERIFICATION
Echanger i = i/2 i=11/2=5 11 22 + 24 16 23 8 22 22 7 1 5 4 10 tab[i] i = i/2 i=5/2=2 Echanger 24 22 23 8 16 22 7 1 5 4 10 tab[i] Avant l’ajout i 1 2 3 4 5 6 7 8 9 10 24 16 23 8 10 22 7 1 5 4 tab[i] while ( tab[i /2] < tab[i ] ) { Echanger ( tab[i], tab[i / 2] ); i = i/2 ; }
1 3 2 4 5 6 7 8 9 10 11 i 1 2 3 4 5 6 7 8 9 10 11 24 22 23 8 16 22 7 1 5 4 10 tab[i] 24 22 23 16 8 22 7 CQFD 10 1 5 4
15 tas tas tas tas + + + 8 15 15 8 2 15 8 8 2 13 15 15 15 15 + 13 2 13 2 14 2 14 5 3 8 2 14 13 13 8 8 8 Exce : Construction d’un tas i 1 2 3 4 5 6 7 8 15 2 13 14 5 3 tab[i]
i 1 2 3 4 5 6 7 15 8 2 13 14 5 3 tab[i] 15 8 2 13 14 5 3 tab[i] 15 13 2 8 14 5 3 15 13 2 8 14 5 3 15 14 2 8 13 5 3 i 1 2 3 4 5 6 7 8 15 2 13 14 5 3 tab[i] int i; ntas ++ ; i =ntas ; tab[i] = val ; while ( ( i > 1 ) && ( tab[i/2] < tab[i] ) ){ Echanger ( tab[i], tab[i / 2] ); i = i/2 ; } 8 15 15 8 tab[2/2] < tab[2]
15 14 5 8 13 2 3 15 14 5 8 13 2 3 15 14 5 15 14 5 8 13 2 3 3 2 13 8 int i; ntas ++ ; i =ntas ; tab[i] = val ; while ( ( i > 1 ) && ( tab[i/2] < tab[i] ) ){ Echanger ( tab[i], tab[i / 2] ); i = i/2 ; } i 1 2 3 4 5 6 7 15 14 2 8 13 5 3 tab[i]
Suppression d’un élément On remplace la valeur du nœud par celle du nœud le plus à droite possible sur le niveau de profondeur le plus élevée, nœud que l’on supprime alors, puis permutations. Exp: racine : suppression du premier élément de la file 24 4 16 23 16 23 8 10 22 7 8 10 22 7 1 5 4 1 5 4
23 16 4 8 10 22 7 23 1 5 16 22 8 10 4 7 1 5 4 16 23 8 10 22 7 1 5