Structures de données IFT-2000 Abder Alikacem Semaine 11 Gestion des arbres binaires de tri et de...

Post on 04-Apr-2015

106 views 0 download

Transcript of Structures de données IFT-2000 Abder Alikacem Semaine 11 Gestion des arbres binaires de tri et de...

Structures de donnéesIFT-2000

Abder AlikacemAbder Alikacem

Semaine 11

Gestion des arbres binaires de tri et de recherche. Les arbres cousus. Les

arbres n-aires

Département d’informatique et de génie logiciel

Édition Septembre 2009

! +

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

Implantation d’un arbre binaire par chaînage

template <typename E>class Arbre{public:

//..private:

// classe Noeudclass 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éesNoeud * racine; //racine de l'arbrelong cpt; // Nombre de noeuds dans l'arbre// Les membres fonctions privés//...

};

...

data

Modèle d’implantation par chaînage

Implantation d’un arbre binaire par chaînage

template <typename E>class Arbre{public:

//ConstructeursArbre(){racine = 0; cpt=0;}Arbre(const Arbre& source) { _auxCopier(source.racine,racine);}

//…

//Destructeur~Arbre() { _auxDetruire(racine);} //…

//Les membres méthodesbool estVide(){ return cpt==0;}long taille() {return cpt;}int hauteur() throw(logic_error); //Par parcoursE max() const throw(logic_error); // allons voir..E min()const throw(logic_error); //…int nbFeuilles()const; //…int nbNoeuds()const; //Taille de l'arbre par parcoursE 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

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érateursvoid operator = (const Arbre & a) {…; _auxCopier(a.racine,racine);}bool operator == (const Arbre &a) { return( _auxArbresEgaux(racine,a.racine));}

//…private:

// classe Nœud//..

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);

//..

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 AVLvoid _zigZigGauche(Noeud *&);void _zigZigDroit(Noeud *&);void _zigZagGauche(Noeud *&);void _zigZagDroit(Noeud *&);int _hauteur(Noeud *);int _maximum(int,int);

};

Ajout d’éléments dans un arbre de tri

1

2

3

6

8

9

10

11

12

8,3,11,2,1,6,10,9,12,4,5,14

1

2

3

6

8

9

10

11

12

4

8,3,11,2,1,6,10,9,12,4,5,14

Ajout d’éléments dans un arbre de tri

1

2

3

6

8

9

10

11

12

4

8,3,11,2,1,6,10,9,12,4,5,14

Ajout d’éléments dans un arbre de tri

1

2

3

6

8

9

10

11

12

4

5

8,3,11,2,1,6,10,9,12,4,5,14

Ajout d’éléments dans un arbre de tri

1

2

3

6

8

9

10

11

12

4

5

8,3,11,2,1,6,10,9,12,4,5,14

Ajout d’éléments dans un arbre de tri

1

2

3

6

8

9

10

11

12

4

5

14

8,3,11,2,1,6,10,9,12,4,5,14

Ajout d’éléments dans un arbre de tri

1

2

3

6

8

9

10

11

12

4

5

14

8,3,11,2,1,6,10,9,12,4,5,14

Ajout d’éléments dans un arbre de tri

1re séquence d’insertions

1

2

3

6

8

9

10

11

12

4

5

14

8,3,11,2,1,6,10,9,12,4,5,14

O(log n)

2e séquence d’insertions

1

2

1,2,3,4,5,6,8,9,10,11,12,14

3

4

5

6

8

O(n/2)

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

template<typename E>void Arbre<E>::inserer(const E &data) throw(bad_alloc){

_auxInserer(racine, data);}

template<typename E>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);}

Insertion sans balancement

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é

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. »

Arbres équilibrés (AVL)

111

2 2

3

Bien équilibré selon la règle AVL

1

2 1

3

Bien équilibré selon la règle AVL

112

3 2

4

Bien équilibré selon la règle AVL

1

12

3 1

4

Mal équilibré selon la règle AVL

1

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.

Équilibration : HB[1]

2 cas (~gauche)

S1

A

B

S2

S3

h + 1

h + 2

h + 3

h + 4N

S1

A

B

S2

S3

h + 1

h + 2

h + 3

h + 4N

Rotation simple

S1

A

B

2

S3

h + 1

h + 2

h + 3

h + 4N

S1

A

B

2

S3

h + 1

h + 2

h + 3

h + 4N

avant après

S2 S2

Rotation simple

S1

A

B

2

S3

h + 1

h + 2

h + 3

h + 4N

S1

A

B

2 S3 h + 1

h + 2

h + 3

h + 4

N

avant après

S2 S2

Rotation simple

S1

A

B

2 S3 h + 1

h + 2

h + 3

h + 4

N

S1

A

B

2 S3 h + 1

h + 2

h + 3

h + 4

N

avant après

S2 S2

S1

A

B

2 S3 h + 1

h + 2

h + 3

h + 4

N

Rotation simple

hauteur initiale vs hauteur finale

S1

A

B

S2

S3

h + 1

h + 2

h + 3

h + 4N

avant après

S2

Rotation double

S1

A

B

S2

S3

h + 1

h + 2

h + 3

h + 4N

S1

A

B

S2.1

S3

h + 1

h + 2

h + 3

h + 4N

S2.2

C

Rotation double

S1

A

B

S2.1

S3

h + 1

h + 2

h + 3

h + 4N

S2.2

C S1

A

B

S2.1

S3

h + 1

h + 2

h + 3

h + 4N

S2.2

C

avant après

Rotation double

S1

A

B

S2.1

S3

h + 1

h + 2

h + 3

h + 4N

S2.2

C S1

B

S2.1

S3 h + 1

h + 2

h + 3

h + 4N

S2.2

C

A

avant après

Rotation double

S1

B

S3 h + 1

h + 2

h + 3

h + 4

N

S2.2

C

S1

B

S3 h + 1

h + 2

h + 3

h + 4

N

C

AA

S2.1 S2.1 S2.2

avant après

Rotation double

S1

B

S3 h + 1

h + 2

h + 3

h + 4

N

S2.2

C

S1

A

B

S2.1

S3

h + 1

h + 2

h + 3

h + 4N

S2.2

C

hauteur initiale vs hauteur finale

A

S2.1

avant après

1re rotation

B

S3

h + 1

h + 2

h + 3

h + 4

N

S2.2S1

A

B

S2.1

S3

h + 1

h + 2

h + 3

h + 4N

S2.2

C

S2.1S1

C

A

avant après

2e rotation

B

S3

h + 1

h + 2

h + 3

h + 4

N

S2.2

S2.1S1

C

A

S1

B

S3 h + 1

h + 2

h + 3

h + 4

N

S2.2

C

A

S2.1

avant après

Nœud critique

Mais ce n’est pas toujours le cas

12

3 1

4

Ici, le nœud critique du déséquilibreest la racine

1

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.

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.

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.

Nouveaunoeud

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.

Nouveaunoeud

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.

Nouveaunoeud

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œudcritique

Nœudsous-critique

Nouveaunoeud

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œudcritique

Nœudsous-critique

Nouveaunoeud

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œudcritique

Nœudsous-critique

Nouveaunoeud

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œudcritique

Nœudsous-critique

Nouveaunoeud

Les rotations

Nœudcritique

Nœudsous-critique

Nouveaunoeud

Première rotation (préparatoire à la deuxième).

Les rotations

Nœudcritique

Nouveaunoeud

Première rotation (préparatoire à la deuxième).

Les rotations

Première rotation (préparatoire à la deuxième).

Nœudcritique

Nouveaunoeud

Les rotations

Première rotation (préparatoire à la deuxième).

Nœudcritique

Nouveaunoeud

Les rotations

Première rotation (préparatoire à la deuxième).

Nœudcritique

Nouveaunoeud

Les rotations

Première rotation (préparatoire à la deuxième).

Nœudcritique

Nouveaunoeud

Les rotations

Première rotation (préparatoire à la deuxième).

Nœudcritique

Ancien nœudsous-critique

Nouveaunoeud

Nœudnouvellementsous-critique

Les rotations

Deuxième rotation.

Nœudcritique

Nouveaunoeud

Les rotations

Deuxième rotation.

Nouveaunoeud

Les rotations

Deuxième rotation.

Nouveaunoeud

Les rotations

Deuxième rotation.

Nouveaunoeud

Les rotations

Deuxième rotation.

Nouveaunoeud

Les rotations

Deuxième rotation.

Nouveaunoeud

Les rotations

Deuxième rotation.

Nouveaunoeud

Les rotations

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

Nouveaunoeud

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é

Modèle d’implantation arbre AVL

template <typename E>class Arbre{public:

//..private:

// classe Noeudclass 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éesNoeud * racine; //racine de l'arbre//...

};

...

data

Modèle d’implantation par chaînage

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.

template <typename E>int Arbre<E>:: _hauteur(Noeud *arb){ if (arb == 0) return -1; return arb->hauteur;}

template <typename E>int Arbre<E>:: _maximum(int ent1, int ent2){ if (ent1 <= ent2) return ent2; else return ent1;}

deux méthodes utiles..

Insertion dans un arbre AVL

Algorithme insereAvl (Nœud* & T, int x) Début

Si T = NULL, alorsDébut

Allocation de mémoire à l'adresse TT.element = x

T. filsG = NULL et T. filsD = NULLT.hauteur = 0

FinSinon 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, alorsAppel ZigZigGauche (T)

SinonAppel ZigZagGauche (T)

Sinon T.hauteur =Max(Hauteur (T.filsG),Hauteur (T.filsD))+1

Sinon Début

/* Cas symétrique pour le sous-arbre droit */ Fin

Fin

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

+

k2k1 k2k

1

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

k2k2k1

k1

algorithme ZigZagGauche (Nœud * &K3)

Début

ZigZigDroit (K3.filsG)

ZigZigGauche (K3)

Fin

+

k3

k3

algorithme ZigZagDroit (Nœud * &K3)

Début

ZigZigGauche (K3.filsD)

ZigZigDroit (K3)

Fin

+

k3k3

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;}

Implémentation d’une rotation

25

45458

48483838

36

2626

17

4040

25 4545

8

48483838

36

262617 4040

1313

28 281313

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:

2 10 12 4 16 8 6 14

AVL – exemple détaillé2 10 12 4 16 8 6 14

2 0

AVL – exemple détaillé

2 10 12 4 16 8 6 14

2

10 0

-1

AVL – exemple détaillé

2 10 12 4 16 8 6 14

2

10

12 0

-1

-2

Nœud critique

AVL – exemple détaillé

2 10 12 4 16 8 6 14

10

122

Rotation simple

00

0

AVL – exemple détaillé

2 10 12 4 16 8 6 14

10

122

4 0

-1 0

1

AVL – exemple détaillé

2 10 12 4 16 8 6 14

10

122

4 16 00

-1-1

0

AVL – exemple détaillé

2 10 12 4 16 8 6 14

10

122

4 16

8 0

-1

-2

0

-1

1

Nœud critique

AVL – exemple détaillé

2 10 12 4 16 8 6 14

10

124

8 162

Rotation simple

0 0 0

-10

0

AVL – exemple détaillé

2 10 12 4 16 8 6 14

10

124

8 162

6 0

10

-1

0

-1

1

AVL – exemple détaillé

2 10 12 4 16 8 6 14

10

124

8 162

6 140 0

1

-2

10

-1

0

Nœud critique

AVL – exemple détaillé

2 10 12 4 16 8 6 14

10

144

8 162

6

12

Rotation double

0

1

00

0

0

-1

1

AVL – exemple détaillé

Voici un exemple où la rotation se fait loin du point d’insertion

10

144

8 162

6

12

-1

1

0 0

0

1

-1

2

10 9 0

70

Noeud inséré

Nœud critique

AVL – exemple détaillé

Voici un exemple où la rotation se fait loin du point d’insertion

10

14

4

8

16

2 6

12

-1

0

01

-1

10

9

070

Après rotation double

0

0

0

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)

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.

Enlèvement dans un arbre AVL

une feuille : trivial48

Analyse : l ’algorithme de suppression d ’un nœud présente donc 3 cas :

34

3020

23 5O

45

gauche

48

droit

29 48

34

3020

23 5O

45

29

Enlèvement dans un arbre AVL

un nœud simple : on le remplace par son unique fils

50

Deuxième cas de nœud à supprimer34

3020

23 5O

45

gauche

48

droit

29

50

34

3020

23

48

29

45

Enlèvement dans un arbre AVL

23 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

Troisième cas de nœud à supprimer34

3020

23 5O

45

gauche

48

droit

29

23

5O

34

3020 45

48

29

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.

5050

2525

7070

13

4545 8585

8

48482828 3939

36

60

2626

64 90905555

6217 4040

3838

3737

89

Puis, nous établissons qu’il s’agit d’un cas compliqué car le nœud à supprimer a deux enfants.

5050

2525

7070

13

4545 8585

8

48482828 3939

36

60

2626

64 90905555

6217 4040

3838

3737

Exemple d’enlèvement AVL

90

Il faut donc d’abord retrouver son successeur à l’aide d’une boucle simple (une fois à droite, plein de fois à gauche).

5050

2525

7070

13

4545 8585

8

48482828 3939

36

60

2626

64 90905555

6217 4040

3838

37

Exemple d’enlèvement AVL

91

Puis on l’échange avec.

5050

2525

7070

13

4545 8585

8

48482828 3939

36

60

2626

64 90905555

6217 4040

3838

37

Exemple d’enlèvement AVL

92

Puis on l’échange avec.

5050

2525

7070

13

4545 8585

8

48482828 3939

36

60

2626

64 90905555

6217 4040

3838

37

Exemple d’enlèvement AVL

93

Puis on l’échange avec.

5050

2525

7070

13

4545 8585

8

48482828 3939

36

60

2626

64 90905555

6217 4040

3838

37

Exemple d’enlèvement AVL

94

Puis on l’échange avec.

5050

2525

7070

13

4545 8585

8

48482828 3939

3660

2626

64 90905555

6217 4040

3838

37

Exemple d’enlèvement AVL

95

Puis on l’échange avec.

5050

2525

7070

13

4545 8585

8

48482828 3939

3660

2626

64 90905555

6217 4040

3838

37

Exemple d’enlèvement AVL

96

Puis on l’échange avec.

5050

2525

7070

13

4545 8585

8

48482828 393936

60

2626

64 90905555

6217 4040

3838

37

Exemple d’enlèvement AVL

97

Puis on l’échange avec.

5050

2525

7070

13

4545 8585

8

48482828 393936

60

2626

64 90905555

6217 4040

3838

37

Exemple d’enlèvement AVL

98

Puis on l’échange avec.

5050

2525

7070

13

4545 8585

8

48482828 3939

36

60

2626

64 90905555

6217 4040

3838

37

Exemple d’enlèvement AVL

99

Puis on l’échange avec.

5050

2525

7070

13

4545 8585

8

48482828 3939

36

60

2626

64 90905555

6217 4040

3838

37

Exemple d’enlèvement AVL

100

Puis on l’échange avec.

5050

2525

7070

13

4545 8585

8

48482828 3939

36

60

2626

64 90905555

6217 4040

3838

37

Exemple d’enlèvement AVL

101

Puis on l’échange avec.

5050

2525

7070

13

4545 8585

8

48482828 3939

36

60

2626

64 90905555

6217 4040

3838

37

Exemple d’enlèvement AVL

102

Puis on l’échange avec.

5050

2525

7070

13

4545 8585

8

48482828 3939

36

60

2626

64 90905555

6217 4040

3838

37

Exemple d’enlèvement AVL

103

Puis on l’échange avec.

5050

2525

7070

13

4545 8585

8

48482828 3939

36

60

2626

64 90905555

6217 4040

3838

37

Exemple d’enlèvement AVL

104

Puis on l’échange avec.

5050

2525

7070

13

4545 8585

8

48482828 3939

36

60

2626

64 90905555

6217 4040

3838

37

Exemple d’enlèvement AVL

105

Puis on l’échange avec.

5050

2525

7070

13

4545 8585

8

48482828 3939

36

60

2626

64 90905555

6217 4040

3838

37

Exemple d’enlèvement AVL

106

Puis on l’échange avec.

5050

2525

7070

13

4545 8585

8

48482828 3939

36

60

2626

64 90905555

6217 4040

3838

37

Exemple d’enlèvement AVL

107

Puis on l’échange avec.

5050

2525

7070

13

4545 8585

8

48482828 3939

36

60

2626

64 90905555

6217 4040

3838

37

Exemple d’enlèvement AVL

108

Remarquez que la règle d’ordonnancement d’arbre binaire de recherche est temporairement enfreinte.

5050

2525

7070

13

4545 8585

8

48482828 3939

36

60

2626

64 90905555

6217 4040

3838

37

Exemple d’enlèvement AVL

109

Ensuite, on continue à descendre récursivement pour supprimer 36, comme si rien n’était.

5050

2525

7070

13

4545 8585

8

48482828 3939

36

60

2626

64 90905555

6217 4040

3838

3737

Exemple d’enlèvement AVL

110

Puis lorsqu’on retombe sur 36, on arrive nécessairement à l’un des deux cas simple.

5050

2525

7070

13

4545 8585

8

48482828 3939

36

60

2626

64 90905555

6217 4040

3838

3737

Exemple d’enlèvement AVL

111

Dans ce cas-ci, il s’agit du cas avec un seul enfant. 36 sera donc remplacé par 38.

5050

2525

7070

13

4545 8585

8

48482828 3939

36

60

2626

64 90905555

6217 4040

38

3737

Exemple d’enlèvement AVL

112

Dans ce cas-ci, il s’agit du cas avec un seul enfant. 36 sera donc remplacé par 38.

5050

2525

7070

13

4545 8585

8

48482828 3939

36

60

2626

64 90905555

6217 4040

38

3737

Exemple d’enlèvement AVL

113

Dans ce cas-ci, il s’agit du cas avec un seul enfant. 36 sera donc remplacé par 38.

5050

2525

7070

13

4545 8585

8

48482828 3939

36

60

2626

64 90905555

6217 404038

3737

Exemple d’enlèvement AVL

114

Dans ce cas-ci, il s’agit du cas avec un seul enfant. 36 sera donc remplacé par 38.

5050

2525

7070

13

4545 8585

8

48482828 3939

36

60

2626

64 90905555

6217 404038

3737

Exemple d’enlèvement AVL

115

Dans ce cas-ci, il s’agit du cas avec un seul enfant. 36 sera donc remplacé par 38.

5050

2525

7070

13

4545 8585

8

48482828 3939

60

2626

64 90905555

6217 404038

3737

Exemple d’enlèvement AVL

116

Dans ce cas-ci, il s’agit du cas avec un seul enfant. 36 sera donc remplacé par 38.

5050

2525

7070

13

4545 8585

8

48482828 3939

60

2626

64 90905555

6217 404038

3737

Exemple d’enlèvement AVL

117

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.

5050

2525

7070

13

4545 8585

8

48482828 3939

60

2626

64 90905555

6217 404038

3737

0 0

Exemple d’enlèvement AVL

118

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.

5050

2525

7070

13

4545 8585

8

48482828 39

60

2626

64 90905555

6217 40403838

3737

1 1

Exemple d’enlèvement AVL

119

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.

5050

2525

7070

13

45 8585

8

48482828 3939

60

2626

64 90905555

6217 40403838

3737

2 1

Exemple d’enlèvement AVL

120

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.

5050

2525

7070

13

4545 8585

8

48482828 3939

60

2626

64 90905555

6217 40403838

373 3

Exemple d’enlèvement AVL

121

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

2525

7070

13

4545 8585

8

48482828 3939

60

2626

64 90905555

6217 40403838

3737

4 4

Exemple d’enlèvement AVL

122

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}

Enlèvement dans un arbre de tri

Enlèvement sans balancement

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 droitNoeud * 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;

}}

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)

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;}

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);}

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;}

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);}

template<typename E>int Arbre<E>:: nbNoeuds() const{

return _nbNoeuds(racine);}

template<typename E>int Arbre<E>:: _nbNoeuds(Noeud* arb) const{

if (arb==0)return 0;

return _nbNoeuds(arb->gauche) + _nbNoeuds(arb->droite) + 1;}

template<typename E>int Arbre<E>::nbFeuilles() const{

return _nbFeuilles(racine);}

template<typename E>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;

}

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);}

template<typename E>int Arbre<E>::_hauteurParcours(Noeud * arb) {

if (arb==0)return -1;

return 1 + _maximum(_hauteur(arb->gauche), _hauteur(arb->droite));}

template<typename E>bool Arbre<E>:: appartient(const E &data) {

return _auxAppartient(racine, data)!=0;}

template<typename E>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);

elsereturn _auxAppartient(arbre->droite, data);

}

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;}

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);

}}

template <typename E>E Arbre<E>:: successeur(const E& info) throw(logic_error){

return _successeur(racine, info);}

template <typename E>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;}

}

template <typename E>void Arbre<E>:: lister(E* res, int&ind) const{

_auxEnOrdre(racine, res, ind);}

template <typename E>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);}

}

template <typename E>void Arbre<E>:: lister(vector<E>& v) const throw(bad_alloc){

_auxEnOrdre(racine, v);}

template <typename E>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);}

}

template <typename E>void Arbre<E>::parcourirPreOrdre(void (* traitement)(E &iteme)) const{

_auxPreOrdre(racine,traitement);}

template <typename E>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);

}}template <typename T>void imprime(T &x){

cout << x << " " ;}

int main(){ try {

Arbre<int> ab;ab.insererAVL(5);…

ab.parcourirPreOrdre(imprime);

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);

}}

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;}

template <typename T>void Arbre<T>::_auxDetruire(Noeud *t){

if (t != 0){

_auxDetruire(t->gauche);_auxDetruire(t->droite);delete t;t= 0;

}}

template <typename T>bool Arbre<T>::_auxArbresEgaux (Noeud * premier, Noeud * second){

if(( premier==0) && (second==0))return true;

elseif ((premier!=0) &&(second!=0))

return((premier->data==second->data) &&_auxArbresEgaux(premier->gauche,second-

>gauche) &&_auxArbresEgaux(premier->droite,second-

>droite));else

return false;}

problèmes :

remonter vers les parents :fonction parent à partir d’un nœudretour 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 !

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

Parcours avec pile

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

Arbres cousus

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 */

//…};

Comment reconnaître les cycles ?• utiliser un «tag» avec chaque pointeur

Arbres n-aires (pour n fixe)

critère de branchement multipleexemples ?

<, =, >arbres-Banalyse lexicale : 1re lettre, 2e lettre, etc.…

Arbres n-aires (pour n fixe)

template <typename E>class Arbre{public:

//..private:

// classe Noeudclass Noeud{ public:

E data;Noeud ** fils;int card; Noeud(const E&d ) { …}

};// Les membres donnéesNoeud * racine; //racine de l'arbre//...

};

...

data

Arbres n-aires (pour n variable)

nombre de branchements inconnu au départnombre de branchements très variableexemples ?

structure organisationnelleanalyse lexicaleautres ?

Arbres n-aires variables

modèles d’implantation ?tableau dynamique ou liste de pointeurs

Arbres n-aires variables

modèles d’implantation ?vector ou liste de pointeurs

Arbres n-aires variables

template <typename E>class Arbre{public:

//..private:

// classe Noeudclass Noeud{ public:

E data;vector<Noeud *> fils;int card; Noeud(const E&d ) { …}

};// Les membres donnéesNoeud * racine; //racine de l'arbre//...

};

...

data

Arbres n-aires variables

modèles d’implantation ?tableau dynamique ou liste de pointeurs2 types de pointeurs : 1er fils, frère cadet

Arbres n-aires variables

modèles d’implantation ?tableau dynamique ou liste de pointeurs2 types de pointeurs : 1er fils, frère cadet

Arbres n-aires variables

modèles d’implantation ?tableau dynamique ou liste de pointeurs2 types de pointeurs : 1er fils, frère cadet

Arbres n-aires variables

modèles d’implantation ?tableau dynamique ou liste de pointeurs2 types de pointeurs : 1er fils, frère cadet