d’organiser les données selon un ordre donné (numérique, lexicographique, etc.) ;
d’effectuer des recherches le plus efficacement possible ;
d’accéder à, d’insérer ou de supprimer les données le plus efficacement possible.
Tableaux
Propriétés
On peut ordonner des données dans un tableau mais l’algorithme de tri le plus rapide, pour un jeu de données aléatoires, est en $O(n \; \log n)$ ;
On peut accéder à une donnée en $O(1)$ ;
On peut rechercher une valeur efficacement en utilisant la dichotomie (si le tableau est trié !) : $O(\log n)$ ;
Supprimer ou insérer une valeur n’est pas très efficace : $O(n)$ (sauf à la fin du tableau).
Dictionnaires
Propriétés
Accéder à, supprimer et insérer une valeur se fait très efficacement : $O(1)$ ;
Les données ne sont pas ordonnées.
Remarque
Dans la suite de ce document, on considérera que les valeurs des nœuds sont des entiers.
Un arbre binaire de recherche, ou ABR, est un arbre binaire avec la propriété suivante : quel que soit le nœud $x$, tous les nœuds situés dans le sous-arbre gauche de $x$ ont une valeur inférieure à la valeur du nœud $x$, et tous ceux situés dans son sous-arbre droit ont une valeur supérieure à la valeur du nœud $x$.
Remarque
La définition précédente est récursive.
Cet arbre binaire est-il un arbre binaire de recherche ?
graph TD
A(3) --- B(1)
B --- C(None)
B --- D(2)
D --- E(None)
D --- F(None)
A --- G(4)
G --- H(None)
G --- I(None)
Réponse
oui
Cet arbre binaire est-il un arbre binaire de recherche ?
graph TD
A(3) --- B(2)
B --- C(1)
C --- D(None)
C --- E(None)
B --- F(None)
A --- G(4)
G --- H(None)
G --- I(None)
Réponse
oui
Cet arbre binaire est-il un arbre binaire de recherche ?
graph TD
A(3) --- B(2)
B --- C(None)
B --- D(1)
D --- E(None)
D --- F(None)
A --- G(4)
G --- H(None)
G --- I(None)
Réponse
Non ! La propriété est bien vérifiée pour la racine mais pas pour le sous-arbre gauche.
La définition est récursive !
Les ABR servent à maintenir un ensemble de valeurs ordonnées, avec une meilleure complexité qu’une liste chaînée par exemple. On commence par la recherche d’un élément. La structure de l’ABR permet d’éviter de le parcourir en entier.
Un ABR est un arbre binaire
Le paradigme de programmation utilisé ci-dessous est le paradigme impératif : on crée dans un premier temps une structure élémentaire à l’aide d’une classe et on définit ensuite des fonctions qui manipulent cette structure.
On ne crée pas de structure arbre binaire (accompagnée de méthodes).
Pour représenter un ABR, on va à nouveau utiliser la classe Noeud. Écrire à nouveau le code de cette classe. Les contraintes sur les valeurs des nœuds ne sont pas codées dans la classe.
À quel arbre binaire de recherche ce code correspond-il ?
1
2
3
4
5
6
7
8
# Création d'un arbre binaire de recherchearbre= Noeud(10)arbre.gauche = Noeud(5)arbre.droit = Noeud(15)arbre.gauche.gauche = Noeud(3)arbre.gauche.droit = Noeud(8)arbre.droit.gauche = Noeud(12)arbre.droit.droit = Noeud(18)
Réponse
graph TD
A(10) — B(5)
A — C(15)
B — D(3)
B — E(8)
C — F(12)
C — I(18)
Écrire à nouveau les spécifications des fonctions taille, hauteur et parcours (parcours infixe).
1
2
3
4
defest_vide(n:Noeud)->bool:"""
Teste si le noeud passé en argument est vide ou pas.
"""
Réponse
1
2
3
4
5
defest_vide(n:Noeud)->bool:"""
Teste si un arbre est vide.
"""returnn==None
1
2
3
4
defhauteur(a:Noeud)->int:"""
Retourne la hauteur de l'arbre binaire de recherche a.
"""
Réponse
1
2
3
4
5
6
7
8
9
10
11
12
13
14
defhauteur(n:Noeud)->int:"""
Retourne la profondeur de l'arbre.
"""ifest_vide(n):# Cas de base : si le nœud est vide, il n'est pas dans l'arbre.return-1else:# Recherche récursive dans les sous-arbres gauche et droit.arbre_gauche=hauteur(n.gauche)arbre_droit=hauteur(n.droit)# Détermine la profondeurreturn1+max(arbre_gauche,arbre_droit)
1
2
3
4
deftaille(a:Noeud)->int:"""
Retourne le nombre de noeuds dans l'arbre binaire de recherche.
"""
Réponse
1
2
3
4
5
6
7
8
9
10
11
12
13
14
deftaille(n:Noeud)->int:"""
Retourne le nombre de noeud dans l'arbre.
"""ifest_vide(n):# Cas de base : un arbre vide ne contient aucun noeud.return0else:# Recherche récursive dans les sous-arbres gauche et droit.taille_gauche=taille(n.gauche)taille_droit=taille(n.droit)# Calcul de la taillereturn1+taille_gauche+taille_droit
1
2
3
4
defparcours(n:Noeud)->None:"""
Parcours infixe de l'arbre
"""
Réponse
1
2
3
4
5
6
7
8
9
10
11
defparcours_inf(n:Noeud)->str:""" Parcours en profondeur infixe d'un arbre binaire. """ifest_vide(n):# Cas de base : On atteint un noeud videreturn""else:# Appel récursif à gauche, valeur du nœud, appel récursif à droitechaine=parcours_inf(n.gauche)chaine+=str(n.valeur)chaine+=parcours_inf(n.droit)returnchaine
Vérifier que la fonction parcours affiche les valeurs dans l’ordre croissant.
Rappel
On peut voir le parcours infixe comme la projection de l’arbre sur une droite.
Recherche dans un ABR
Écrire le code de la fonction appartient dont la spécification est
1
2
3
4
defappartient(a:Noeud,x:int)->bool:"""
Détermine si la valeur x apparaît dans l'arbre a.
"""
Réponse
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
defappartient(a:Noeud,x:int)->bool:"""
Détermine si la valeur x apparaît dans l'abr a.
"""ifest_vide(a):# Cas de base : on parvient à un noeud vide,# la valeur n'est pas présentereturnFalseelifa.valeur==x:# Cas de base : on parvient au noeud recherchéreturnTrue# Appels récursifselifa.valeur>x:returnappartient(a.gauche,x)else:returnappartient(a.droit,x)
Quelle est la complexité de cette recherche ?
Réponse
La complexité du pire cas de la fonction appartient dépend de la hauteur de l’arbre binaire de recherche (ABR).
Dans le pire des cas, l’arbre peut être déséquilibré au point de ressembler à une liste chaînée. Dans un tel scénario, la complexité de la recherche est linéaire par rapport au nombre total de nœuds dans l’arbre.
Complexité : $O(n)$.
Si on écarte ce cas extrême, l’arbre binaire de recherche (ABR) est plus ou moins équilibré et la hauteur est de l’ordre du logrithme du nombre de nœuds.
Complexité : $O(\log n)$.
Ajout d’une valeur dans un ABR
Envisager un ABR et, à la main, ajouter un élément.
Quel peut-être le code de la fonction ajoute dont la spécification est
1
2
3
4
5
6
defajoute(a:Noeud,x:int)->None:"""
Ajoute x à l'arbre a (en place).
HYPOTHÈSE : a n'est pas l'arbre vide.
"""
Quelle est la complexité de cette fonction ?
Pour résoudre le problème de l’ajout d’une valeur à un arbre initialement vide et/ou pour ne pas modifier un arbre déjà constitué, il est possible de programmer la fonction ajoute_copie dont la spécification est
1
2
3
4
defajoute_copie(n:Noeud,x:int)->Noeud:"""
Ajoute la valeur x à l'ABR n. Un nouvel arbre est créé.
"""
Écrire le code de cette fonction.
Utilisation d’un arbre binaire de recherche
Créer la fonction plus_petit_element dont la spécification est
1
2
3
4
5
6
defplus_petit_element(a:Noeud)->int:"""
Retourne le plus petit élément de l'arbre binaire de recherche a.
Lève une exception si l'arbre est vide.
"""
Créer la fonction plus_grand_element dont la spécification est
1
2
3
4
5
6
defplus_grand_element(a:Noeud)->int:"""
Retourne le plus grand élément de l'arbre binaire de recherche a.
Lève une exception si l'arbre est vide.
"""
Créer la fonction est_abr dont la spécification est
1
2
3
4
defest_abr(a:Noeud)->bool:"""
Détermine si l'arbre a est un arbre binaire de recherche.
"""
Un arbre binaire de recherche est un arbre binaire contenant des valeurs ordonnées, tel que tout nœud contient une valeur plus grande (respectivement plus grande ou égale) que les valeurs de tous les nœuds situés dans son sous-arbre gauche. Cette organisation permet de procéder par dichotomie, en n’ayant à considérer qu’un seul sous-arbre pendant une opération.
Lorsqu’ils sont équilibrés, les arbres binaires de recherche constituent une méthode très efficace pour stocker et rechercher de l’information.