La présentation est en train de télécharger. S'il vous plaît, attendez

La présentation est en train de télécharger. S'il vous plaît, attendez

Structures de données IFT-2000

Présentations similaires


Présentation au sujet: "Structures de données IFT-2000"— Transcription de la présentation:

1 Structures de données IFT-2000
Abder Alikacem Semaine 11 Gestion des arbres binaires de tri et de recherche. Les arbres cousus. Les arbres n-aires Édition Septembre 2009 Département d’informatique et de génie logiciel

2 Gestion des arbres binaires
Algorithmes de gestion d’un arbre binaire de tri Les arbres de recherche, arbres AVL Algorithme de balancement d’un arbre AVL Les arbres cousus Les arbres n-aires

3 Modèle d’implantation par chaînage
Implantation d’un arbre binaire par chaînage template <typename E> class Arbre { public: //.. private: // classe Noeud class Noeud { public: E data; Noeud *gauche; Noeud *droite; int card; int hauteur; Noeud( const E&d ): gauche(0),data( d ),droite(0),hauteur(0) { } }; // Les membres données Noeud * racine; //racine de l'arbre long cpt; // Nombre de noeuds dans l'arbre // Les membres fonctions privés //... Modèle d’implantation par chaînage ... data

4 Implantation d’un arbre binaire par chaînage
template <typename E> class Arbre { public: //Constructeurs Arbre(){racine = 0; cpt=0;} Arbre(const Arbre& source) { _auxCopier(source.racine,racine);} //… //Destructeur ~Arbre() { _auxDetruire(racine);} //… //Les membres méthodes bool estVide(){ return cpt==0;} long taille() {return cpt;} int hauteur() throw(logic_error); //Par parcours E max() const throw(logic_error); // allons voir.. E min()const throw(logic_error); //… int nbFeuilles()const; //… int nbNoeuds()const; //Taille de l'arbre par parcours E parent(const E&) throw(logic_error); //.. E successeur(const E& ) throw(logic_error); //.. void lister(E*, int&) const; //… void lister(vector<E>&) const; //… //.. L’interface publique

5 Implantation d’un arbre binaire par chaînage
template <typename E> class Arbre { public: //.. bool appartient(const E &); //… void insererAVL(const E &data) throw(bad_alloc); //.. void enleverAVL( const E&) throw(logic_error); //… void parcourirPreOrdre(void (* traitement)(E &iteme)) const; //… void parcourirEnOrdre(void (* traitement)(E &iteme)) const; void parcourirPostOrdre(void (* traitement)(E &iteme)) const; void parcourirParNiveau(void (* traitement)(E &iteme)) const; //… //surcharge d'opérateurs void operator = (const Arbre & a) {…; _auxCopier(a.racine,racine);} bool operator == (const Arbre &a) { return( _auxArbresEgaux(racine,a.racine));} //… private: // classe Nœud

6 Implantation d’un arbre binaire par chaînage
template <typename E> class Arbre { private: //.. Les membres méthodes privés // Les auxiliaires récursifs pour l'insertion et les différents parcours, E _max(Noeud*)const throw (logic_error); E _min(Noeud*)const throw (logic_error); int _hauteurParcours(Noeud *); int _nbFeuilles(Noeud*) const; int _nbNoeuds(Noeud*) const; Noeud* _parent(Noeud*, Noeud*); E _successeur(Noeud*, const E&) throw(logic_error); void _auxEnOrdre(Noeud*, E*, int&) const; void _auxEnOrdre(Noeud*, vector<E>&) const; bool _auxArbresEgaux (Noeud *, Noeud *); //… void _auxCopier( Noeud *, Noeud*&); void _auxDetruire(Noeud *&t); void _auxInserer( Noeud *&, const E&); void Arbre<E>::_auxInsererAVL(Noeud *&arbre, const E &data); //..

7 Implantation d’un arbre binaire par chaînage
template <typename E> class Arbre { private: //.. Les membres fonctions privés, suite… void _auxEnlever( Noeud* &, const E&)throw(logic_error); void _auxRetireMin( Noeud* &) const; Noeud* _auxAppartient(Noeud* & arbre, const E &data); void _auxPreOrdre(Noeud *,void (* traitement)(E&)) const; void _auxEnOrdre(Noeud *,void (* traitement)(E&)) const; void _auxPostOrdre(Noeud *,void (* traitement)(E&)) const; // Les membres privés propres aux arbres AVL void _zigZigGauche(Noeud *&); void _zigZigDroit(Noeud *&); void _zigZagGauche(Noeud *&); void _zigZagDroit(Noeud *&); int _hauteur(Noeud *); int _maximum(int,int); };

8 Ajout d’éléments dans un arbre de tri
8 3 11 2 6 10 12 1 9 8,3,11,2,1,6,10,9,12,4,5,14

9 Ajout d’éléments dans un arbre de tri
8 3 11 2 6 10 12 1 4 9 8,3,11,2,1,6,10,9,12,4,5,14

10 Ajout d’éléments dans un arbre de tri
8 3 11 2 6 10 12 1 4 9 8,3,11,2,1,6,10,9,12,4,5,14

11 Ajout d’éléments dans un arbre de tri
8 3 11 2 6 10 12 1 4 9 5 8,3,11,2,1,6,10,9,12,4,5,14

12 Ajout d’éléments dans un arbre de tri
8 3 11 2 6 10 12 1 4 9 5 8,3,11,2,1,6,10,9,12,4,5,14

13 Ajout d’éléments dans un arbre de tri
8 3 11 2 6 10 12 1 4 9 14 5 8,3,11,2,1,6,10,9,12,4,5,14

14 Ajout d’éléments dans un arbre de tri
8 3 11 2 6 10 12 1 4 9 14 5 8,3,11,2,1,6,10,9,12,4,5,14

15 1re séquence d’insertions
O(log n) 8 3 11 2 6 10 12 1 4 9 14 5 8,3,11,2,1,6,10,9,12,4,5,14

16 2e séquence d’insertions
1 O(n/2) 2 3 4 5 6 8 1,2,3,4,5,6,8,9,10,11,12,14

17 Analyse Le prix d’une opération (recherche, insertion, retrait) est proportionnel au nombre de noeuds visités Donc, coût proportionnel à 1+hauteur de l’arbre (un coût par niveau) Meilleur cas: arbre équilibré (les feuilles à peu près toutes à la même profondeur) insertion et retrait aléatoire tendent à créer un arbre équilibré profondeur = O(log n) Pire cas: liste chaînée par exemple lors de l’insertion d’éléments ordonnés profondeur = n Donc, le coût est O(log n) dans le meilleur cas et O(n) dans le pire cas

18 Insertion sans balancement
template<typename E> void Arbre<E>::inserer(const E &data) throw(bad_alloc) { _auxInserer(racine, data); } void Arbre<E>::_auxInserer(Noeud *&arbre, const E &data) if (arbre == 0) arbre = new Noeud(data); cpt++; else if(arbre->data > data ) _auxInserer(arbre->gauche, data); else _auxInserer(arbre->droite, data);

19 Arbres équilibrés – concepts de base
Situation idéale visée: s’assurer que le sous-arbre de gauche et le sous-arbre de droite sont de même hauteur Ce principe s’appliquerait à tous les noeuds de manière récursive Si on appliquait ceci à chaque insertion ou retrait, ce serait très coûteux Il faut donc établir des conditions plus faibles, mais qui nous assurent des gains en performance, nous verrons cela lorsqu’on parlera des arbres rouge et noir. Arbre bien équilibré Arbre mal équilibré

20 Arbres binaires équilibrés (AVL)
Rappel C’est un arbre de recherche binaire tel que pour chaque noeud, les hauteurs des ses sous-arbres gauche et droite sont différentes d’au plus k, k étant le critère d’équilibre (on attribue comme hauteur la valeur -1 pour un sous-arbre vide). K =1 dans le cas des arbres AVL. Avec cette condition, on est assuré de toujours avoir un arbre dont la profondeur est proportionnelle à log (n). arbres AVL = HB[1] (arbres Adelson-Velski et Landis) arbres HB[k] « Un arbre T est HB[k] si T et tous ses sous-arbres ont la propriété HB[k] qui est : les sous-arbres gauche et droit diffèrent en hauteur d’au plus k. »

21 Arbres équilibrés (AVL)
Bien équilibré selon la règle AVL Bien équilibré selon la règle AVL 3 3 2 2 2 1 1 1 1 1 Bien équilibré selon la règle AVL Mal équilibré selon la règle AVL 4 4 3 2 3 1 1 2 1 2 1 1 1

22 Arbres équilibrés (AVL)
Il faut, après chaque insertion ou retrait, rétablir l’équilibre s’il a été rompu par l’opération. Observation importante: après une insertion, seuls les noeuds qui sont sur le chemin du point d’insertion à la racine sont susceptibles d’être déséquilibrés. Deux cas: insertion dans le sous-arbre de gauche du fils gauche ou dans le sous-arbre de droite du fils droit:  Simple rotation insertion dans le sous-arbre de droite du fils gauche ou dans le sous-arbre de gauche du fils droit:  Double rotation On doit distinguer 4 cas en tout, deux cas à gauche et deux cas à droite.

23 Équilibration : HB[1] 2 cas (~gauche) A A B B S 3 S 3 h + 1 h + 1 S 1
4 N h + 4

24 Rotation simple avant après A A B B S 3 S 3 h + 1 h + 1 S 1 S2 2 S 1
4 N h + 4 avant après

25 Rotation simple avant après A B A B S 3 S 1 h + 1 h + 1 S 1 S2 2 S2 2
4 h + 4 avant après

26 Rotation simple avant après B B A A S 1 S 1 h + 1 h + 1 S2 2 S 3 S2 2
4 h + 4 avant après

27 Rotation simple avant après hauteur initiale vs hauteur finale A B A B
3 S 1 h + 1 h + 1 S 1 S 2 S2 2 S 3 h + 2 h + 2 h + 3 N h + 3 N h + 4 h + 4 avant après

28 Rotation double A A B B S 3 S 3 h + 1 h + 1 S 1 S 2 S 1 C h + 2 h + 2
4 N h + 4

29 Rotation double avant après A A B B S 3 S 3 C h + 1 h + 1 S 1 S 1 C h
2 h + 2 S2.1 S2.2 S2.1 S2.2 h + 3 h + 3 N h + 4 N h + 4 avant après

30 Rotation double avant après A C B B A S 3 S 1 C h + 1 S3 h + 1 S 1 h +
2 h + 2 S2.1 S2.2 S2.1 S2.2 h + 3 h + 3 N h + 4 N h + 4 avant après

31 Rotation double avant après C C A B A B S2.1 S2.2 S3 h + 1 h + 1 S 1 S
4 h + 4 avant après

32 Rotation double avant après hauteur initiale vs hauteur finale A C B B
3 C h + 1 S2.2 h + 1 S 1 S 1 S2.1 S3 h + 2 h + 2 S2.1 S2.2 N h + 3 h + 3 N h + 4 h + 4 avant après

33 1re rotation avant après A A B C S 3 S3 C h + 1 B S2.2 h + 1 S 1 h + 2
4 h + 4 avant après

34 2e rotation avant après A C C B A S3 B S2.2 h + 1 h + 1 S 1 S2.1 S2.2
4 h + 4 avant après

35 Nœud critique Quand il y a un déséquilibre, le nœud le plus bas à partir duquel il y a une différence de 2 ou plus est appelé le nœud critique. Ici, le nœud critique du déséquilibre est la racine Mais ce n’est pas toujours le cas 4 3 1 2 1 1

36 Nœud critique Il peut parfois y avoir plusieurs nœuds débalancés. Dans ce cas, on s’occupe d’abord du plus bas de tous, c’est le nœud critique.

37 Maintien de l’équilibre
Quand on implémente un arbre AVL, il faut maintenir son équilibre. Les déséquilibres surviennent soit lors d’un ajout, soit lors d’une suppression. Le ou les nœuds critiques engendrés, s’il y a lieu, sont toujours sur le chemin de l’ajout ou de la suppression. Nouveau noeud

38 Rééquilibrer un arbre déséquilibré
Quand un déséquilibre apparaît, il faut remodeler la partie de l’arbre dont la racine est le nœud critique. Nouveau noeud

39 Les quatre cas de déséquilibre
Il faut d’abord identifier le genre de déséquilibre auquel on a affaire. D’abord, il faut voir de quel côté l’arbre penche à partir du nœud critique. Dans le cas de l’exemple ci-bas, c’est vers la gauche. Nouveau noeud

40 Les quatre cas de déséquilibre
L’enfant immédiat du nœud critique du côté vers lequel l’arbre penche s’appelle le nœud sous-critique. Nœud critique Nœud sous-critique Nouveau noeud

41 Les quatre cas de déséquilibre
Puis il faut regarder de quel côté penche l’arbre à partir du nœud sous-critique. L’arbre sous-critique n’a pas besoin d’être déséquilibré pour qu’on considère qu’il penche. Une différence de 1 suffit pour identifier le cas (contrairement à la vérification qu’on faisait au départ pour vérifier s’il y avait déséquilibre). Dans l’exemple ci-bas, l’arbre sous-critique penche vers la droite. Nœud critique Nœud sous-critique Nouveau noeud

42 Les quatre cas de déséquilibre
Nous avons donc, dans cet exemple-ci, un déséquilibre vers la gauche, avec un arbre sous-critique qui penche vers la droite. Quand le sens du déséquilibre principal est différent du sens dans lequel l’arbre sous-critique penche, alors deux rotations sont nécessaires. Nous appelons ça un zig-zag. Quand le sens du déséquilibre principal est le même que le sens dans lequel l’arbre sous-critique penche, ou bien que l’arbre sous-critique ne penche pas dutout, alors une seule rotation est nécessaire, et il s’agit d’un zig-zig. Dans notre exemple, nous aurons donc à faire deux rotations. Nœud critique Nœud sous-critique Nouveau noeud

43 Les rotations Quand nous nous retrouvons dans le cas où il faut faire deux rotations, la première sert finalement à faire pencher l’arbre sous-critique dans le même sens que l’arbre critique, de façon à nous retrouver dans le cas simple d’une seule rotation. Nœud critique Nœud sous-critique Nouveau noeud

44 Les rotations Première rotation (préparatoire à la deuxième). Nœud
critique Nœud sous-critique Nouveau noeud

45 Les rotations Première rotation (préparatoire à la deuxième). Nœud
critique Nouveau noeud

46 Les rotations Première rotation (préparatoire à la deuxième). Nœud
critique Nouveau noeud

47 Les rotations Première rotation (préparatoire à la deuxième). Nœud
critique Nouveau noeud

48 Les rotations Première rotation (préparatoire à la deuxième). Nœud
critique Nouveau noeud

49 Les rotations Première rotation (préparatoire à la deuxième). Nœud
critique Nouveau noeud

50 Ancien nœud sous-critique
Les rotations Première rotation (préparatoire à la deuxième). Nœud critique Nœud nouvellement sous-critique Nouveau noeud Ancien nœud sous-critique

51 Les rotations Deuxième rotation. Nœud critique Nouveau noeud

52 Les rotations Deuxième rotation. Nouveau noeud

53 Les rotations Deuxième rotation. Nouveau noeud

54 Les rotations Deuxième rotation. Nouveau noeud

55 Les rotations Deuxième rotation. Nouveau noeud

56 Les rotations Deuxième rotation. Nouveau noeud

57 Les rotations Deuxième rotation. Nouveau noeud

58 Les rotations Les deux rotations sont terminées, nous avons maintenant un arbre AVL équilibré. Nouveau noeud

59 Implémentation : remarques
indice de débalancement : tag = hauteur(gauche) - hauteur(droit) valeurs : 2, 1, 0, -1, -2 calcul de hauteur(gauche) - hauteur(droit) au parcours d’insertion + stockage de la hauteur mise à jour un seul rebalancement requis la racine de l’arbre rebalancé a changé

60 Modèle d’implantation par chaînage
Modèle d’implantation arbre AVL template <typename E> class Arbre { public: //.. private: // classe Noeud class Noeud { public: E data; Noeud *gauche; Noeud *droite; int card; int hauteur; Noeud( const E&d ): gauche(0),data( d ),droite(0),hauteur(0) { } }; // Les membres données Noeud * racine; //racine de l'arbre //... Modèle d’implantation par chaînage ... data

61 Implémentation de l’insertion dans un arbre AVL
Algorithme récursif Une fois le noeud inséré, en revenant sur notre chemin, il faut vérifier, pour chaque noeud parcouru, les différences de profondeur des sous-arbres gauche et droite. La rotation peut être requise à n’importe quel noeud qui se trouve dans le chemin de la racine au point d’insertion.

62 Insertion dans un arbre AVL
template <typename E> int Arbre<E>:: _maximum(int ent1, int ent2) { if (ent1 <= ent2) return ent2; else return ent1; } template <typename E> int Arbre<E>:: _hauteur(Noeud *arb) { if (arb == 0) return -1; return arb->hauteur; } deux méthodes utiles..

63 Algorithme insereAvl (Nœud* & T, int x)
Début Si T = NULL, alors Allocation de mémoire à l'adresse T T.element = x T. filsG = NULL et T. filsD = NULL T.hauteur = 0 Fin Sinon Début Si x < T.element, alors Appel insereAvl(T.filsG, x) Si Hauteur (T.filsG)-Hauteur (T.filsD) = 2 alors Si x < T. filsG.element, alors Appel ZigZigGauche (T) Appel ZigZagGauche (T) T.hauteur =Max(Hauteur (T.filsG),Hauteur (T.filsD))+1 /* Cas symétrique pour le sous-arbre droit */ Fin

64 + k2 k1 k1 k2 algorithme ZigZigGauche (Nœud* &K2) Début K1 = K2.filsG
K2.filsG = K1.filsD K1.filsD = K2 K2.hauteur = Max (Hauteur (K2.filsG), Hauteur (K2.filsD)) +1 K1.hauteur = Max (Hauteur (K1.filsG), K2.hauteur) +1 K2 = K1 Fin k2 k1 k1 k2 +

65 k1 k2 k1 k2 algorithme ZigZigDroit (Nœud* &K2) Début K1 = K2.filsD
K2.filsD = K1.filsG K1.filsG = K2 K2.hauteur = Max (Hauteur (K2.filsD), Hauteur (K2.filsG)) +1 K1.hauteur = Max (Hauteur (K1.filsD), K2.hauteur) +1 K2 = K1 Fin simple rotation, déséquilibre vers la gauche k1 k2 k1 k2

66 + k3 k3 algorithme ZigZagGauche (Nœud * &K3) Début
ZigZigDroit (K3.filsG) ZigZigGauche (K3) Fin k3 k3 +

67 + k3 k3 algorithme ZigZagDroit (Nœud * &K3) Début
ZigZigGauche (K3.filsD) ZigZigDroit (K3) Fin k3 k3 +

68 Implémentation d’une rotation
template <typename E> void Arbre<E>:: _zigZigDroit(Noeud * &K2) { Noeud *K1; K1 = K2->droite; K2->droite = K1->gauche; K1->gauche = K2; K2->hauteur = 1 + _maximum(_hauteur(K2->droite), _hauteur(K2->gauche)); K1->hauteur = 1 + _maximum(_hauteur(K1->droite), K2->hauteur); K2 = K1; } 25 36 36 13 25 45 8 17 28 45 13 28 38 48 26 38 48 8 17 26 40 40

69 AVL – exemple détaillé Pour chaque noeud on mettra
0 si ses deux sous-arbres ont la même hauteur +1 si le sous-arbre gauche est plus profond avec une différence = 1 -1 si le sous-arbre droit est plus profond avec une différence = 1 Séquence d’insertion:

70 AVL – exemple détaillé 2

71 AVL – exemple détaillé 2 -1 10

72 AVL – exemple détaillé Nœud critique 2 -2 10 -1 12

73 AVL – exemple détaillé 10 2 12 Rotation simple

74 AVL – exemple détaillé 10 1 2 12 -1 4

75 AVL – exemple détaillé 10 2 12 -1 -1 4 16

76 AVL – exemple détaillé 2 10 12 4 16 8 6 14 10 Nœud critique 2 12 4 16
10 1 Nœud critique 12 2 -1 -2 4 16 -1 8

77 AVL – exemple détaillé 10 4 12 -1 2 8 16 Rotation simple

78 AVL – exemple détaillé 10 1 4 12 -1 -1 2 8 16 1 6

79 AVL – exemple détaillé 2 10 12 4 16 8 6 14 10 Nœud critique 4 12 2 8
10 Nœud critique 4 -1 12 -2 2 8 16 1 1 6 14

80 AVL – exemple détaillé 10 1 4 14 -1 2 8 12 16 1 6 Rotation double

81 AVL – exemple détaillé Voici un exemple où la rotation se fait loin du point d’insertion Nœud critique 2 10 4 14 -1 2 8 12 16 1 1 1 6 9 -1 7 Noeud inséré

82 AVL – exemple détaillé Voici un exemple où la rotation se fait loin du point d’insertion 8 4 -1 10 14 2 6 9 1 -1 12 16 1 7 Après rotation double

83 Analyse insertion balancée : trouver le point d’insertion : O(log n)
insertion d’une feuille : O(1) + vérification et rebalancement: on remonte (suite aux appels récursifs) : O(log n) on vérifie le rebalancement possible : O(1) on rebalance au besoin : O(1) total : O(log n)

84 Enlèvement dans un arbre AVL
Pour supprimer un nœud dans un arbre AVL, il y a deux cas simples et un cas compliqué: Premier cas simple: le nœud à supprimer est une feuille. Dans ce cas, il suffit de le supprimer directement. Deuxième cas simple: le nœud à supprimer possède un seul enfant. Dans ce cas, il suffit de le supprimer et de le remplacer par son seul enfant. Cas compliqué: le nœud à supprimer a deux enfants. Dans ce cas, il faut d’abord échanger ce nœud avec son successeur, puis le supprimer à son nouvel endroit, ce qui nous mènera nécessairement à l’un des deux cas simples. Bien entendu, il faut aussi vérifier les déséquilibres en remontant jusqu’à la racine. L’algorithme fonctionne aussi bien si on prend le prédécesseur plutôt que le successeur. Étant donné que la nécessité de retrouver le successeur ne survient que dans le cas où le nœud a deux enfants, alors nous sommes nécessairement toujours en présence du cas simple de recherche du successeur! Ainsi, une simple boucle suffit.

85 Enlèvement dans un arbre AVL
34 Analyse : l ’algorithme de suppression d ’un nœud présente donc 3 cas : gauche droit 30 20 23 5O une feuille : trivial 48 34 45 30 20 23 5O 29 48 48 45 29

86 Enlèvement dans un arbre AVL
34 Deuxième cas de nœud à supprimer gauche droit un nœud simple : on le remplace par son unique fils 50 30 20 23 5O 50 34 45 30 20 23 45 29 48 48 29

87 Enlèvement dans un arbre AVL
34 Troisième cas de nœud à supprimer gauche droit un nœud double : on lui donne la valeur minimale de son sous-arbre droit (ex: 29), et on supprime le nœud qui a cette valeur 23 30 20 23 23 5O 34 45 29 5O 29 48 20 30 45 48

88 Exemple d’enlèvement AVL
Supprimons le nœud 36 de cet arbre-ci. Il faut d’abord le repérer avec une recherche conventionnelle à partir de la racine. 50 36 70 25 60 85 45 13 28 39 55 64 90 48 8 17 26 37 40 62 38

89 Exemple d’enlèvement AVL
Puis, nous établissons qu’il s’agit d’un cas compliqué car le nœud à supprimer a deux enfants. 50 36 70 25 60 85 45 13 28 39 55 64 90 48 8 17 26 37 40 62 38

90 Exemple d’enlèvement AVL
Il faut donc d’abord retrouver son successeur à l’aide d’une boucle simple (une fois à droite, plein de fois à gauche). 50 36 70 25 60 85 45 13 28 39 55 64 90 48 8 17 26 37 40 62 38

91 Exemple d’enlèvement AVL
Puis on l’échange avec. 50 36 70 25 60 85 45 13 28 39 55 64 90 48 8 17 26 37 40 62 38

92 Exemple d’enlèvement AVL
Puis on l’échange avec. 50 36 70 25 60 85 45 13 28 39 55 64 90 48 8 17 26 37 40 62 38

93 Exemple d’enlèvement AVL
Puis on l’échange avec. 50 70 36 25 60 85 45 13 28 39 55 64 90 48 37 8 17 26 40 62 38

94 Exemple d’enlèvement AVL
Puis on l’échange avec. 50 70 36 25 60 85 45 13 28 39 37 55 64 90 48 8 17 26 40 62 38

95 Exemple d’enlèvement AVL
Puis on l’échange avec. 50 70 25 60 85 45 36 37 13 28 39 55 64 90 48 8 17 26 40 62 38

96 Exemple d’enlèvement AVL
Puis on l’échange avec. 50 70 37 25 60 85 45 36 13 28 39 55 64 90 48 8 17 26 40 62 38

97 Exemple d’enlèvement AVL
Puis on l’échange avec. 50 37 70 25 60 85 45 13 28 39 55 64 90 48 36 8 17 26 40 62 38

98 Exemple d’enlèvement AVL
Puis on l’échange avec. 50 37 70 25 60 85 45 13 28 39 55 64 90 48 36 8 17 26 40 62 38

99 Exemple d’enlèvement AVL
Puis on l’échange avec. 50 37 70 25 60 85 45 13 28 39 55 64 90 48 8 17 26 36 40 62 38

100 Exemple d’enlèvement AVL
Puis on l’échange avec. 37 50 70 25 60 85 45 13 28 39 55 64 90 48 8 17 26 36 40 62 38

101 Exemple d’enlèvement AVL
Puis on l’échange avec. 37 50 70 25 60 85 45 13 28 39 55 64 90 48 8 17 26 36 40 62 38

102 Exemple d’enlèvement AVL
Puis on l’échange avec. 37 50 70 25 60 85 45 13 28 39 55 64 90 48 8 17 26 36 40 62 38

103 Exemple d’enlèvement AVL
Puis on l’échange avec. 37 50 70 25 60 85 45 13 28 39 55 64 90 48 8 17 26 36 40 62 38

104 Exemple d’enlèvement AVL
Puis on l’échange avec. 50 37 70 25 60 85 45 13 28 39 55 64 90 48 8 17 26 36 40 62 38

105 Exemple d’enlèvement AVL
Puis on l’échange avec. 50 37 70 25 60 85 45 13 28 39 55 64 90 48 8 17 26 36 40 62 38

106 Exemple d’enlèvement AVL
Puis on l’échange avec. 50 37 70 25 60 85 45 13 28 39 55 64 90 48 8 17 26 36 40 62 38

107 Exemple d’enlèvement AVL
Puis on l’échange avec. 50 37 70 25 60 85 45 13 28 39 55 64 90 48 8 17 26 36 40 62 38

108 Exemple d’enlèvement AVL
Remarquez que la règle d’ordonnancement d’arbre binaire de recherche est temporairement enfreinte. 50 37 70 25 60 85 45 13 28 39 55 64 90 48 8 17 26 36 40 62 38

109 Exemple d’enlèvement AVL
Ensuite, on continue à descendre récursivement pour supprimer 36, comme si rien n’était. 50 37 70 25 60 85 45 13 28 39 55 64 90 48 8 17 26 36 40 62 38

110 Exemple d’enlèvement AVL
Puis lorsqu’on retombe sur 36, on arrive nécessairement à l’un des deux cas simple. 50 37 70 25 60 85 45 13 28 39 55 64 90 48 8 17 26 36 40 62 38

111 Exemple d’enlèvement AVL
Dans ce cas-ci, il s’agit du cas avec un seul enfant. 36 sera donc remplacé par 38. 50 37 70 25 60 85 45 13 28 39 55 64 90 48 8 17 26 36 40 62 38

112 Exemple d’enlèvement AVL
Dans ce cas-ci, il s’agit du cas avec un seul enfant. 36 sera donc remplacé par 38. 50 37 70 25 60 85 45 13 28 39 55 64 90 48 8 17 26 40 62 36 38

113 Exemple d’enlèvement AVL
Dans ce cas-ci, il s’agit du cas avec un seul enfant. 36 sera donc remplacé par 38. 50 37 70 25 60 85 45 13 28 39 55 64 90 48 8 17 26 40 62 38 36

114 Exemple d’enlèvement AVL
Dans ce cas-ci, il s’agit du cas avec un seul enfant. 36 sera donc remplacé par 38. 50 37 70 25 60 85 45 13 28 39 55 64 90 48 8 17 26 40 62 38 36

115 Exemple d’enlèvement AVL
Dans ce cas-ci, il s’agit du cas avec un seul enfant. 36 sera donc remplacé par 38. 50 37 70 25 60 85 45 13 28 39 55 64 90 48 8 17 26 38 40 62 36

116 Exemple d’enlèvement AVL
Dans ce cas-ci, il s’agit du cas avec un seul enfant. 36 sera donc remplacé par 38. 50 37 70 25 60 85 45 13 28 39 55 64 90 48 8 17 26 38 40 62 36

117 Exemple d’enlèvement AVL
Puis le nœud 36 est détruit avec « delete ». Ensuite, il faut remonter jusqu’à la racine pour vérifier l’équilibre de l’arbre. 50 37 70 25 60 85 45 13 28 39 55 64 90 48 8 17 26 38 40 62

118 Exemple d’enlèvement AVL
Puis le nœud 36 est détruit avec « delete ». Ensuite, il faut remonter jusqu’à la racine pour vérifier l’équilibre de l’arbre. 50 37 70 25 60 85 45 13 28 39 55 64 90 48 1 1 8 17 26 38 40 62

119 Exemple d’enlèvement AVL
Puis le nœud 36 est détruit avec « delete». Ensuite, il faut remonter jusqu’à la racine pour vérifier l’équilibre de l’arbre. 50 37 70 25 60 85 45 2 1 13 28 39 55 64 90 48 8 17 26 38 40 62

120 Exemple d’enlèvement AVL
Puis le nœud 36 est détruit avec « delete ». Ensuite, il faut remonter jusqu’à la racine pour vérifier l’équilibre de l’arbre. 50 37 70 3 3 25 60 85 45 13 28 39 55 64 90 48 8 17 26 38 40 62

121 Exemple d’enlèvement AVL
Puis le nœud 36 est détruit avec « delete ». Ensuite, il faut remonter jusqu’à la racine pour vérifier l’équilibre de l’arbre. 50 4 4 37 70 25 60 85 45 13 28 39 55 64 90 48 8 17 26 38 40 62

122 Enlèvement dans un arbre de tri
Enlèvement sans balancement template <typename E> void Arbre<E>::enlever(const E& data) throw(logic_error) { if( racine == 0 ) throw logic_error("Enlever: l'arbre est vide\n"); if( _auxAppartient(racine, data) == 0 ) throw logic_error("Enlever: l'element n’est pas dans l'arbre\n"); _auxEnlever(racine, data); //data est certain dans l'arbre }

123 template <typename E>
void Arbre<E>:: _auxEnlever(Noeud * & t, const E & valeur) throw(logic_error) { if( t->data > valeur) _auxEnlever( t->gauche, valeur); else if( t->data < valeur ) _auxEnlever( t->droite, valeur); else if( t->gauche != 0 && t->droite != 0 ) {//Troisième cas //chercher le noeud qui contient la valeur minimale dans le sous-arbre droit Noeud * temp = t->droite; while ( temp->gauche != 0) temp = temp->gauche; t->data = temp->data; _auxRetireMin( t->droite ); // Retirer minimum dans le sous-arbre droit } else { //Premier ou deuxième cas // le noeud n'a aucun enfant ou qu'un seul enfant, il suffit donc de retirer // ce noeud et pointer sur l'éventuel enfant Noeud * vieuxNoeud = t; t = ( t->gauche != 0 ) ? t->gauche : t->droite; delete vieuxNoeud;

124 template <typename E>
void Arbre<E>:: _auxRetireMin( Noeud* & t) const throw(logic_error) { if (t == 0) throw logic_error("_auxRetireMin: pointeur NULL\n"); else if (t->gauche != 0) _auxRetireMin( t->gauche ); else Noeud * tmp = t; t = t->droite; delete tmp; }

125 Analyse enlèvement balancé :
plusieurs rebalancements possiblement requis la racine de tout arbre rebalancé a changé trouver le nœud à enlever (déjà vu) : O(log n) + vérification et rebalancement: on remonte (suite aux appels récursifs) : O(log n) on vérifie le rebalancement possible : O(1) on rebalance au besoin : O(1) total : O(log n) (mais plus coûteux que l’insertion)

126 template<typename E>
E Arbre<E>::max()const throw (logic_error) { if (cpt==0) throw logic_error("Max: l'arbre est vide!\n"); if (racine->droite == 0) return racine->data; } Noeud * temp = racine->droite; while (temp->droite!=0) temp = temp->droite; return temp->data;

127 template<typename E>
E Arbre<E>::_max(Noeud*racine)const throw (logic_error) { if (cpt==0) throw logic_error("Max: l'arbre est vide!\n"); if (racine->droite == 0) return racine->data; } return _max(racine->droite);

128 template<typename E>
E Arbre<E>::min()const throw (logic_error) { if (cpt==0) throw logic_error("Min: l'arbre est vide!\n"); if (racine->gauche == 0) return racine->data; } Noeud * temp = racine->gauche; while (temp->gauche!=0) temp = temp->gauche; return temp->data;

129 template<typename E>
E Arbre<E>::_min(Noeud*racine) const throw (logic_error) { if (racine==0) throw logic_error("Min: l'arbre est vide!\n"); if (racine->gauche == 0) return racine->data; } return _min(racine->gauche);

130 template<typename E>
int Arbre<E>:: nbNoeuds() const { return _nbNoeuds(racine); } int Arbre<E>:: _nbNoeuds(Noeud* arb) const if (arb==0) return 0; return _nbNoeuds(arb->gauche) + _nbNoeuds(arb->droite) + 1;

131 template<typename E>
int Arbre<E>::nbFeuilles() const { return _nbFeuilles(racine); } int Arbre<E>::_nbFeuilles(Noeud*arb) const int nbG (0), nbD(0); if (arb != 0) if (arb->gauche == 0 && arb->droite == 0) return 1; else if (arb->gauche != 0) nbG = _nbFeuilles(arb->gauche); if (arb->droite != 0) nbD = _nbFeuilles(arb->droite); return nbG + nbD;

132 template<typename E>
int Arbre<E>::hauteur() throw (logic_error) { if (cpt==0) throw logic_error("Hauteur: l'arbre est vide!\n"); return _hauteurParcours(racine); } int Arbre<E>::_hauteurParcours(Noeud * arb) if (arb==0) return -1; return 1 + _maximum(_hauteur(arb->gauche), _hauteur(arb->droite));

133 template<typename E>
bool Arbre<E>:: appartient(const E &data) { return _auxAppartient(racine, data)!=0; } typename Arbre<E>:: Noeud* Arbre<E>:: _auxAppartient(Noeud* & arbre, const E &data) if (arbre == 0) return 0; if ( arbre->data == data ) return arbre; if ( arbre->data > data ) return _auxAppartient(arbre->gauche, data); else return _auxAppartient(arbre->droite, data);

134 template <typename E>
E Arbre<E>:: parent(const E& el) throw(logic_error) { Noeud* noeudDeEl = _auxAppartient(racine, el); Noeud* parentDeEl = _parent(racine, noeudDeEl); return parentDeEl->data; }

135 template <typename E>
typename Arbre<E>:: Noeud* Arbre<E>:: _parent(Noeud* arb, Noeud* sArb) throw(logic_error) { if (arb == 0) throw logic_error("Parent: l'arbre est vide!\n"); if (sArb == 0) throw logic_error("Parent: l'element n'existe pas!\n"); if (sArb == arb) throw logic_error("Parent: Le parent de la racine d'existe pas!\n"); if ( sArb->data < arb-> data ) if (arb->gauche == sArb) return arb; else return _parent(arb->gauche, sArb); } else if (arb->droite == sArb) return arb; else return _parent(arb->droite, sArb);

136 template <typename E>
E Arbre<E>:: successeur(const E& info) throw(logic_error) { return _successeur(racine, info); } E Arbre<E>:: _successeur(Noeud* arb, const E& info) throw (logic_error) if (cpt == 0) throw logic_error("Successeur: l'arbre est vide!\n"); Noeud* sArb = _auxAppartient(racine, info); if (sArb == 0) throw logic_error("Successeur: l'element n'existe pas!\n"); if ( info == _max(arb)) throw logic_error("Successeur: l'element est le max dans l'arbre!\n"); if (sArb->droite != 0) return _min(sArb->droite); else Noeud * pere = _parent(arb, sArb); while (pere->data < sArb->data ) pere = _parent(arb,pere); return pere->data;

137 template <typename E>
void Arbre<E>:: lister(E* res, int&ind) const { _auxEnOrdre(racine, res, ind); } void Arbre<E>:: _auxEnOrdre(Noeud* arb, E* res, int&ind) const if (arb == 0) return; else _auxEnOrdre(arb->gauche, res, ind); /* traitement(arb) */ res[ind] = arb->data; ind++; _auxEnOrdre(arb->droite, res, ind);

138 template <typename E>
void Arbre<E>:: lister(vector<E>& v) const throw(bad_alloc) { _auxEnOrdre(racine, v); } void Arbre<E>:: _auxEnOrdre(Noeud* arb, vector<E>& v) const if (arb == 0) return; else _auxEnOrdre(arb->gauche, v); /* traitement(arb) */ v.push_back(arb->data); _auxEnOrdre(arb->droite, v);

139 template <typename E>
void Arbre<E>::parcourirPreOrdre(void (* traitement)(E &iteme)) const { _auxPreOrdre(racine,traitement); } void Arbre<E>::_auxPreOrdre(Noeud*arbre,void(*traitement)(E &iteme))const if (arbre !=0) traitement(arbre->data); _auxPreOrdre(arbre->gauche, traitement); _auxPreOrdre(arbre->droite, traitement); int main() { try { Arbre<int> ab; ab.insererAVL(5); ab.parcourirPreOrdre(imprime); template <typename T> void imprime(T &x) { cout << x << " " ; }

140 template <typename E>
void Arbre<E>::parcourirParNiveau(void (* traitement)(E &iteme)) const { queue<Noeud*> Q; Noeud * temp; Q.push(racine); while (!Q.empty()) temp= Q.front(); traitement(temp->data); Q.pop(); if(temp->gauche!=0)Q.push(temp->gauche); if(temp->droite!=0)Q.push(temp->droite); }

141 template <typename T>
void Arbre<T>::_auxCopier( Noeud * source, Noeud * & destination) throw(bad_alloc) { if (source!=0) destination = new Noeud(source->data); destination->hauteur = source->hauteur; _auxCopier(source->gauche, destination->gauche); _auxCopier(source->droite, destination->droite); } else destination=0;

142 template <typename T>
void Arbre<T>::_auxDetruire(Noeud *t) { if (t != 0) _auxDetruire(t->gauche); _auxDetruire(t->droite); delete t; t= 0; }

143 template <typename T>
bool Arbre<T>::_auxArbresEgaux (Noeud * premier, Noeud * second) { if(( premier==0) && (second==0)) return true; else if ((premier!=0) &&(second!=0)) return((premier->data==second->data) && _auxArbresEgaux(premier->gauche,second->gauche) && _auxArbresEgaux(premier->droite,second->droite)); return false; }

144 Parcours avec pile problèmes : remonter vers les parents :
fonction parent à partir d’un nœud retour par les appels récursifs espace à gérer : accès par pile : quelle est la taille de la pile ? combien de piles a-t-on besoin ? pour de nombreux usagers = trop d’espace !

145 Parcours avec pile Peut-on éliminer les piles ?
ajouter un pointeur vers le parent problèmes : ne jamais perdre le parent lors de l’ajout beaucoup d’espace perdu complique les rebalancements

146 Arbres cousus Peut-on éliminer les piles ?
ajouter un pointeur vers le parent ajouter un pointeur vers le succ./préd. problèmes : - complique les ajouts - complique les rebalancements - il faut éviter les cycles

147 Arbres cousus Comment reconnaître les cycles ?
utiliser un «tag» avec chaque pointeur class Noeud { public: E data; /* élément */ Noeud *gauche; /* ptr sur SAG ou prédécesseur */ Noeud *droite; /* ptr sur SAD ou successeur */ bool filGauche; /* indique si gauche est un fil ou non */ bool filDroit; /* indique si droite est un fil ou non */ //… };

148 Arbres n-aires (pour n fixe)
critère de branchement multiple exemples ? <, =, > arbres-B analyse lexicale : 1re lettre, 2e lettre, etc.

149 Arbres n-aires (pour n fixe)
template <typename E> class Arbre { public: //.. private: // classe Noeud class Noeud { public: E data; Noeud ** fils; int card; Noeud(const E&d ) { …} }; // Les membres données Noeud * racine; //racine de l'arbre //... ... data

150 Arbres n-aires (pour n variable)
nombre de branchements inconnu au départ nombre de branchements très variable exemples ? structure organisationnelle analyse lexicale autres ?

151 Arbres n-aires variables
modèles d’implantation ? tableau dynamique ou liste de pointeurs

152 Arbres n-aires variables
modèles d’implantation ? vector ou liste de pointeurs

153 Arbres n-aires variables
template <typename E> class Arbre { public: //.. private: // classe Noeud class Noeud { public: E data; vector<Noeud *> fils; int card; Noeud(const E&d ) { …} }; // Les membres données Noeud * racine; //racine de l'arbre //... ... data

154 Arbres n-aires variables
modèles d’implantation ? tableau dynamique ou liste de pointeurs 2 types de pointeurs : 1er fils, frère cadet

155 Arbres n-aires variables
modèles d’implantation ? tableau dynamique ou liste de pointeurs 2 types de pointeurs : 1er fils, frère cadet

156 Arbres n-aires variables
modèles d’implantation ? tableau dynamique ou liste de pointeurs 2 types de pointeurs : 1er fils, frère cadet

157 Arbres n-aires variables
modèles d’implantation ? tableau dynamique ou liste de pointeurs 2 types de pointeurs : 1er fils, frère cadet


Télécharger ppt "Structures de données IFT-2000"

Présentations similaires


Annonces Google