Arbres⚓︎
Arbres binaires de recherche (ABR)⚓︎
Définition d'un ABR
Un arbre binaire de recherche est un arbre binaire dont les valeurs des nœuds (valeurs qu'on appelle étiquettes, ou clés) vérifient la propriété suivante :
- l'étiquette d'un nœud est supérieure ou égale à celle de chaque nœud de son sous-arbre gauche.
- l'étiquette d'un nœud est strictement inférieure à celle du chaque nœud de son sous-arbre droit.
À noter que l'arbre 3 (qui est bien un ABR) est appelé arbre filiforme.
L'arbre 5 n'est pas un ABR à cause de la feuille 9, qui fait partie du sous-arbre gauche de 3 sans lui être inférieure.
Remarque : on pourrait aussi définir un ABR comme un arbre dont le parcours infixe est une suite croissante.
Exercice
1. Complétez cet ABR en y insérant dans l'ordre les clés 2, 15, 29 puis 28.
2. Quel est le résultat du parcours de cet arbre suivant l'ordre infixe ?
On retrouve les clés dans l'ordre croissant : 2,5,10,12,15,17,19,20,22,24,28,29
To Do
Créer un nouveau dossier ABR, et dupliquer votre fichiers de la classe Arbre
Nous allons en réutiliser la majeure partie, puisque un arbre binaire de recherche est un arbre binaire particulier.
Créer un nouveau fichier de test avec les deux arbres suivants :
question : Les 2 arbres sont ils des arbres binaires de recherche ?
#arbre a
a = Arbre(5)
a.gauche = Arbre(2)
a.droit = Arbre(7)
a.gauche.gauche = Arbre(0)
a.gauche.droit = Arbre(3)
a.droit.gauche = Arbre(6)
a.droit.droit = Arbre(8)
#arbre b
b = Arbre(3)
b.gauche = Arbre(2)
b.droit = Arbre(5)
b.gauche.gauche = Arbre(1)
b.gauche.droit = Arbre(9)
b.droit.gauche = Arbre(4)
b.droit.droit = Arbre(6)
b n'est pas un arbre binaire de recherche à cause du 9 et du 4.
1. Déterminer si un arbre est un ABR⚓︎
Employer une méthode récursive imposerait de garder en mémoire dans l'exploration des sous-arbres la valeur maximale ou minimale. Nous allons plutôt utiliser la remarque précédente, et nous servir du parcours infixe.
Méthode : récupérer le parcours infixe dans une liste, et faire un test sur cette liste.
Être ou ne pas être un ABR
🐍 Script Python | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
assert a.est_ABR() == True
assert b.est_ABR() == False
Exercice
ré-équilibrer l'arbre b pour que cela devienne un arbre binaire de recherche et tester avec votre fonction est_ABR()
c = Arbre(3)
c.gauche = Arbre(2)
c.droit = Arbre(5)
c.gauche.gauche = Arbre(1)
c.droit.gauche = Arbre(4)
c.droit.droit = Arbre(9)
c.droit.droit.gauche = Arbre(9)
assert c.est_ABR() == True
2. Rechercher une clé dans un ABR⚓︎
Un arbre binaire de taille \(n\) contient \(n\) clés (pas forcément différentes). Pour savoir si une valeur particulière fait partie des clés, on peut parcourir tous les nœuds de l'arbre, jusqu'à trouver (ou pas) cette valeur dans l'arbre. Dans le pire des cas, il faut donc faire \(n\) comparaisons.
Mais si l'arbre est un ABR, le fait que les valeurs soient «rangées» va considérablement améliorer la vitesse de recherche de cette clé, puisque la moitié de l'arbre restant sera écartée après chaque comparaison.
Recherche d'une clé dans un ABR
def recherche_ABR(self, valeur):
if self is None :
return False
if self.noeud == valeur :
return True
if valeur < self.noeud and not self.gauche is None :
return self.gauche.recherche_ABR( valeur)
elif valeur > self.noeud and not self.droit is None :
return self.droit.recherche_ABR(valeur)
return False
Exemple
L'arbre a
contient la valeur 8, mais pas la valeur 4.
#Test de la fonction de recherche dans un arbre binaire de recherche
assert a.recherche_ABR(5) == True #Cas ou la valeur recherchée est la racine
assert a.recherche_ABR(0) == True #Cas ou la valeur recherchée est une feuille
assert a.recherche_ABR(4) == False #Cas ou la valeur recherchée n'est pas dans l'arbre
assert a.recherche_ABR(8) == True #Cas ou la valeur recherchée est une feuille et la plus à droite
3. Coût de la recherche dans un ABR équilibré⚓︎
Imaginons un arbre équilibré de taille \(n\). Combien d'étapes faudra-t-il, dans le pire des cas, pour trouver (ou pas) une clé particulière dans cet arbre ?
Après chaque nœud, le nombre de nœuds restant à explorer est divisé par 2. On retrouve là le principe de recherche dichotomique, vu en classe de Première (voir ici).
S'il faut parcourir tous les étages de l'arbre avant de trouver (ou pas) la clé recherchée, le nombre de nœuds parcourus est donc égal à la hauteur \(h\) de l'arbre.
Pour un arbre complet, cette hauteur vérifie la relation \(2^h -1= n\). et donc \(2^h = n+1\).
\(h\) est donc le «nombre de puissance de 2» que l'on peut mettre dans \(n+1\). Cette notion s'appelle le logarithme de base 2 et se note \(\log_2\).
Par exemple, \(\log_2(64)=6\) car \(2^6=64\).
Le nombre maximal de nœuds à parcourir pour rechercher une clé dans un ABR équilibré de taille \(n\) est donc de l'ordre de \(\log_2(n)\), ce qui est très performant !
Pour arbre contenant 1000 valeurs, 10 étapes suffisent.
Cette complexité logarithmique est un atout essentiel de la structure d'arbre binaire de recherche.
4. Insertion dans un ABR⚓︎
L'insertion d'une clé va se faire au niveau d'une feuille, donc au bas de l'arbre. Dans la version récursive de l'algorithme d'insertion, que nous allons implémenter, il n'est pourtant pas nécessaire de descendre manuellement dans l'arbre jusqu'au bon endroit : il suffit de distinguer dans lequel des deux sous-arbres gauche et droit doit se trouver la future clé, et d'appeler récursivement la fonction d'insertion dans le sous-arbre en question.
Algorithme :
- Si l'arbre est vide, on renvoie un nouvel objet Arbre contenant la clé.
- Sinon, on compare la clé à la valeur du nœud sur lequel on est positionné :
- Si la clé est inférieure à cette valeur, on va modifier le sous-arbre gauche en le faisant pointer vers ce même sous-arbre une fois que la clé y aura été injectée, par un appel récursif.
- Si la clé est supérieure, on fait la même chose avec l'arbre de droite.
- on renvoie le nouvel arbre ainsi créé.
Insertion dans un ABR
def insertion(self, cle):
if self is None :
# si l'arbre est vide, on crée un arbre avec la clé
return Arbre(cle)
if self.gauche is None and cle <= self.noeud :
self.gauche = Arbre(cle)
return self
if self.droit is None and cle > self.noeud :
self.droit = Arbre(cle)
return self
if cle <= self.noeud :
return self.gauche.insertion(cle)
else :
return self.droit.insertion(cle)
Exemple : Nous allons insérer la valeur 4 dans l'arbre a
et vérifier par un parcours infixe (avant et après l'insertion) que la valeur 4 a bien été insérée au bon endroit.
a = Arbre(5)
a.gauche = Arbre(2)
a.droit = Arbre(7)
a.gauche.gauche = Arbre(0)
a.gauche.droit = Arbre(3)
a.droit.gauche = Arbre(6)
a.droit.droit = Arbre(8)
>>> a.infixe()
0-2-3-5-6-7-8-
>>> a.insertion(4)
<__main__.Arbre at 0x7f46f0507e80>
>>> a.infixe()
0-2-3-4-5-6-7-8-
Bibliographie⚓︎
- En majeur partie sur la trame du cours de Gilles Lassus – Enseignant NSI
Lycée François Mauriac -- Bordeaux
- Cours de Frédéric Mandon
- NSI 24 Leçons avec exercices corrigés – Edition Ellipses
- Prépabac NSI, Terminale, G.CONNAN, V.PETROV, G.ROZSAVOLGYI, L.SIGNAC, éditions HATIER.
- Numérique et Sciences Informatiques, Terminale, T. BALABONSKI, S. CONCHON, J.-C. FILLIATRE, K. NGUYEN, éditions ELLIPSES.