Vous êtes sur la page 1sur 8

Arbre binaire de recherche

I. Introduction
L’arbre binaire de recherche (noté ABR) est l’équivalent pour les arbres binaires de ce qu’est le
tableau trié pour les tableaux (voir l’introduction du cours sur les arbres binaires). Dans un ABR la
valeur de chaque nœud est supérieure ou égale à la valeur des nœuds de son sous-arbre gauche et
inférieure ou égale à la valeur des nœuds de son sous-arbre droit.

II. Recherche d’un élément dans un ABR


Étant donné un entier x, on souhaite rechercher un nœud de valeur x dans l’ABR.

A. Version récursive

Décomposition
Rechercher un nœud de valeur x dans un ABR revient à
- rechercher un nœud de valeur x dans le sous-arbre gauche si x est inférieur à la valeur de la racine,
- rechercher un nœud de valeur x dans le sous-arbre droit si x est supérieur à la valeur de la racine,
- arrêter la recherche si la valeur de la racine est égale à x.

Cas de base
Si l’ABR est vide, il ne contient pas de nœud de valeur x.

Dominique Schmitt L3 Informatique et Mathématiques 1


// Renvoie l’adresse d’un noeud de valeur x dans l’ABR de racine r
// ou un pointeur nul si x n’est pas présent dans l’ABR
Noeud* ArbreBinaire::chercheABR(Noeud *r, int x) const
{
if (r == nullptr)
return nullptr;
else
if (x < r->info)
return chercheABR(r->fg,x);
else
if (x > r->info)
return chercheABR(r->fd,x);
else // x == r->info
return r;
}

Première simulation : recherche du nœud de valeur 80 dans l’ABR donné dans la section I.

Supposons que la fonction chercheABR est appelée avec comme paramètre r le pointeur
numéroté 1 sur la figure. Comme r n’est pas nul et que 80 est inférieur à r->info = 99, la fonction est
relancée récursivement sur le pointeur numéroté 2. Ce pointeur devient le paramètre r du nouvel
appel à la fonction. Comme ce pointeur r n’est pas nul et que 80 est supérieur à r->info = 40, la
fonction est relancée récursivement sur le pointeur numéroté 3. Ce pointeur est le paramètre r du
3ème appel à la fonction. Comme il n’est pas nul et que r->info = 80, la fonction s’arrête et renvoie le
pointeur r à l’appel récursif précédent. Cet appel renvoie ce pointeur au premier appel qui le renvoie
lui aussi.

Dominique Schmitt L3 Informatique et Mathématiques 2


Deuxième simulation : recherche du nœud de valeur 15 dans l’ABR de la section I.

Lors de la recherche de la valeur 15, la fonction est relancée récursivement sur les pointeurs
numérotés 1, 2, 3 puis 4. Ce dernier étant un pointeur nul, la fonction renvoie nullptr pour
signifier que la valeur 15 n’est pas présente dans l’arbre. En effet, si la valeur 15 était présente dans
l’ABR, elle ne pourrait se trouver que dans le sous-arbre gauche du nœud de valeur 99, puisqu’elle
est inférieure à 99. Comme la valeur 15 est aussi inférieure à 40, elle ne peut se trouver que dans le
sous-arbre gauche de 40. Comme la valeur 15 est supérieure à 7, elle ne peut se trouver que dans le
sous-arbre droit de 7. Comme le nœud de valeur 7 n’a pas de sous-arbre droit, il en résulte que la
valeur 15 n’est pas présente dans l’arbre.

B. Version itérative
Nous avons jusqu’à présent écrit toutes nos fonctions de traitement d’arbres binaires de manière
récursive. C’est la méthode la plus naturelle pour parcourir un arbre. Lorsque le sous-arbre gauche
d’un nœud a été traité, il nous faut en effet un moyen de remonter à ce nœud pour pouvoir ensuite
traiter son sous-arbre droit. Comme les nœuds ne contiennent pas de lien vers leur père, la méthode
la plus simple pour pouvoir remonter au père après avoir traité le sous-arbre gauche est d’appeler
récursivement la fonction sur le sous-arbre gauche. Quand elle aura fini de traiter ce sous-arbre, elle
reviendra automatiquement là où elle a été appelée, c'est-à-dire au père.

La fonction chercheABR n’a pas besoin de revenir en arrière. Elle ne parcourt en effet qu’une seule
branche de l’arbre, c'est-à-dire un chemin qui mène de la racine à un nœud. La fonction peut donc
être écrite de façon itérative avec la méthode suivante : descendre dans l’arbre (soit à gauche, soit à
droite) tant que la valeur recherchée n’a pas été trouvée et qu’un sous-arbre vide n’a pas été atteint.
Le parcours de l’arbre se fait alors comme le parcours d’une liste chainée, à l’aide d’un pointeur. En
outre, comme la fonction n’est pas appelée récursivement sur un sous-arbre, il est inutile de lui
passer la racine de l’arbre en paramètre.

Dominique Schmitt L3 Informatique et Mathématiques 3


// Recherche itérative d’un noeud de valeur x dans l’ABR auquel la
// méthode est appliquée
Noeud* ArbreBinaire::chercheABR(int x) const
{
Noeud* p = racine; // le pointeur qui sert à parcourir la
// branche
while (p != nullptr && p->info != x)
{
if (x < p->info)
p = p->fg;
else
p = p ->fd;
}
// A ce point soit p est nul, soit p pointe sur un noeud de
// valeur x. Dans les 2 cas on renvoie p.
return p;
}

Lors de la recherche du nœud de valeur 80 dans l’arbre de la section I, le pointeur p pointe


successivement sur les nœuds de valeur 99, 40 et 80.

C. Complexité de la recherche
Les versions itérative et récursive parcourent les mêmes nœuds de l’arbre et ont donc la même
complexité. Le pire des cas se produit lorsque :
1) soit l’élément recherché existe dans l’arbre et se trouve dans sa feuille la plus profonde,
2) soit l’élément recherché n’existe pas dans l’arbre mais, s’il existait, il serait un fils de la feuille la
plus profonde.

Dominique Schmitt L3 Informatique et Mathématiques 4


Dans l’arbre de la section I, le cas 1) se produit lors de la recherche de l’élément 64. Le cas 2) se
produit lors de la recherche d’un élément de l’intervalle ]52, 64[ ou de l’intervalle ]64, 80[. Dans le
pire des cas la recherche est donc en O(h), où h est la hauteur de l’arbre.

Donner la complexité de la fonction par rapport à la hauteur de l’arbre n’a que peu d’intérêt. Si nous
voulons comparer la complexité de la recherche d’un élément dans un ABR avec la recherche d’un
élément dans d’autres structures de données, la complexité doit être exprimée en fonction du
nombre n d’éléments stockés dans la structure. Nous devons donc évaluer la hauteur h de notre
arbre en fonction du nombre n de nœuds qu’il contient.

Étant donné un entier i  [0,…,h], l’ensemble des nœuds de profondeur i forment ce qu’on appelle le
niveau i de l’arbre. Clairement, moins les niveaux de l’arbre sont remplis, plus l’arbre est haut.

1. Valeur maximale de h en fonction de n


La valeur maximale de h est atteinte lorsque chaque niveau de l’arbre ne contient qu’un seul nœud.
Dans un tel arbre chaque nœud n’a qu’un seul fils (à part le nœud le plus profond qui n’en a aucun).

La hauteur h de l’arbre est alors égale à n-1 et la complexité de la fonction est en O(n), ce qui n’est
pas bon pour un algorithme de recherche. Rappelons qu’on sait rechercher un élément en temps
O(log n) dans un tableau trié.

2. Valeur minimale de h en fonction de n


La valeur minimale de h pour un n donné est atteinte lorsque tous les niveaux de l’arbre, à part le
niveau h, sont complètement remplis.

Il est facile de vérifier que dans ce cas, pour tout i  [0,…,h-1], le niveau i contient 2i nœuds. Le
niveau h contient au-moins un nœud (sinon l’arbre ne serait pas de hauteur h) et en contient au plus
2h. Comme le nombre n de nœuds de l’arbre est égal à la somme des nombres de nœuds présents
dans tous ses niveaux, on obtient :

h−1 h

∑ 2 < n ≤ ∑ 2i
i

i=0 i=0

2h -1 < n ≤ 2h+1 -1

2h ≤ n < 2h+1

Dominique Schmitt L3 Informatique et Mathématiques 5


log22h ≤ log2n < log22h+1

h ≤ log2n < h+1

Il en résulte que h est l’entier immédiatement inférieur ou égal à log2n, c'est-à-dire h = ⌊log 2 n⌋. Nous
venons donc de montrer que la hauteur minimale d’un arbre de n nœuds est égale à ⌊log 2 n⌋. Plus
généralement, on dit qu’un arbre est équilibré si sa hauteur est en O(log n), c'est-à-dire
proportionnelle à log n.

La complexité de la recherche d’un élément dans un ABR équilibré est donc en O(log n). Comme nous
l’avons vu dans le cours sur la recherche dichotomique, ceci est optimal. L’ABR est donc une
structure de données efficace pour la recherche d’éléments, à condition qu’il soit équilibré.

III. Insertion d’un élément dans un ABR


Nous souhaitons maintenant insérer un entier x dans l’ABR. Il faut pour cela créer un nouveau nœud
de valeur x et le placer à un endroit approprié dans l’arbre de telle sorte que celui-ci conserve les
propriétés d’un ABR. Si l’entier x est déjà présent dans l’arbre il sera tout de même ajouté, puisqu’un
ABR peut contenir plusieurs fois la même valeur.

A. Version récursive
Décomposition
Insérer l’élément de valeur x dans un ABR revient à
- insérer l’élément de valeur x dans le sous-arbre gauche si x est inférieur à la valeur de la racine,
- insérer l’élément de valeur x dans le sous-arbre droit si x est supérieur à la valeur de la racine,
- insérer l’élément de valeur x dans n’importe quel sous-arbre si x est égal à la valeur de la racine.

Cas de base
Si l’arbre est vide, l’élément à insérer devient la racine de l’arbre.

// Insertion de l’entier x dans l’ABR de racine r


void ArbreBinaire::insereABR(Noeud *&r, int x)
{
if (r == nullptr)
r = new Noeud(x);
else
if (x < r->info)
insereABR(r->fg,x);
else // x >= r->info
insereABR(r->fd,x);
}

Dominique Schmitt L3 Informatique et Mathématiques 6


Simulation : insertion de la valeur 90 dans l’ABR de la section I.

Lors de l’insertion de la valeur 90 dans l’arbre de la section I, la fonction est lancée récursivement
avec, comme paramètre r, les pointeurs numérotés 1, 2, 3 puis 4. Lorsque la fonction est lancée avec
pour paramètre r le pointeur numéroté 4 qui est un pointeur nul, un nouveau nœud est créé et le
pointeur r est modifié pour pointer sur ce nouveau nœud. Comme le pointeur r est transmis par
référence à la fonction, c’est le pointeur original qui est modifié, c'est-à-dire le champ fd du nœud de
valeur 80.

B. Version itérative
La fonction d’insertion ne parcourt elle aussi qu’une seule branche de l’arbre et peut donc être écrite
de manière itérative. Si on utilise la même méthode que dans la fonction de recherche, cela revient à
utiliser un pointeur p, à le faire descendre dans l’arbre jusqu’à ce qu’il soit nul et à créer ensuite un
nouveau nœud pour faire pointer p dessus. Cela ne fonctionne pas, car le pointeur p est une variable
locale de la fonction. Le pointeur qui doit pointer sur le nouveau nœud créé est un des pointeurs
présents dans l’arbre. Dans l’exemple ci-dessus, la descente doit s’arrêter lorsque p pointe sur le
nœud de valeur 80, pour pouvoir ensuite modifier le pointeur fd de ce nœud. L’écriture de la
fonction avec cette technique est relativement lourde ; je vous laisse vous en convaincre par vous-
même.

Une méthode itérative plus pratique consiste non pas à déclarer p comme un pointeur de nœud,
mais comme un pointeur de pointeur de nœud (Noeud** p). On le fait alors descendre dans
l’arbre jusqu’à ce qu’il pointe sur un pointeur nul (et non pas jusqu’à ce qu’il soit égal à un pointeur
nul). Dans l’exemple ci-dessus, la boucle s’arrête lorsque p pointe sur le pointeur fd qui se trouve
dans le nœud de valeur 80. On pourra alors modifier fd en passant par p. Je laisse à ceux qui le
souhaitent le soin d’implémenter cette fonction.

Dominique Schmitt L3 Informatique et Mathématiques 7


C. Complexité
Le pire des cas se produit lorsque l’élément inséré devient un fils de la feuille la plus profonde de
l’arbre. La complexité est alors linéaire avec la hauteur de l’arbre, c'est-à-dire en O(log n) si l’arbre
est équilibré.

Même si l’arbre est initialement équilibré, il risque de ne plus l’être après plusieurs insertions
d’éléments effectuées par la fonction précédente. Il faut donc régulièrement rééquilibrer l’arbre. Il
existe des méthodes qui permettent de rééquilibrer un arbre en temps O(log n) après une insertion.
Ceux qui souhaitent en savoir plus sur le sujet peuvent trouver des informations complémentaires en
effectuant une recherche sur les arbres AVL.

Il est important de noter ici, qu’aucune des autres structures de données qui ont été vues jusque-là
en licence ne permet d’effectuer à la fois la recherche d’un élément et l’insertion d’un élément en
temps O(log n). Nous verrons dans le prochain cours que la suppression d’un élément dans un ABR
équilibré peut également se faire en temps O(log n).

Dominique Schmitt L3 Informatique et Mathématiques 8

Vous aimerez peut-être aussi