4.1 KiB
Principe On prend pour hypothèse que les clés sont ordonnées. Un arbre binaire possède la propriété "de recherche" si tous ses nœuds vérifient les deux propositions suivantes :
- l'étiquette de ce nœud est plus grande que les étiquettes de tous les nœuds de son sous-arbre gauche.
- l'étiquette de ce nœud est plus petite que les étiquettes de tous les nœuds de son sous-arbre droit.
Cette forme permet d'obtenir toutes les étiquettes dans l'ordre croissant par un parcours en profondeur infixe, donc elle est équivalente à une liste triée. Il est plus facile de maintenir cette propriété lors d'un ajout ou d'une suppression que pour une structure linéaire, car de nombreuses formes sont possibles.
Un ABR repose sur une règle de structure stricte : pour chaque nœud, les éléments à gauche sont plus petits et les éléments à droite sont plus grands.
Sans l'interface Comparable, l'arbre ne saurait pas comment ranger un objet personnalisé (comme une classe Etudiant ou Produit), car il ne saurait pas si Etudiant A est "avant" Etudiant B.
Une autre application de cette structure est la recherche d'élément. Il suffit de descendre dans l'arbre une seule fois pour savoir si une valeur donnée est présente.
Le coût de cette recherche est proportionnel à la hauteur de l'arbre.
La hauteur minimum d'un arbre binaire à n nœuds est log2n et la hauteur maximum est n. On peut équilibrer un arbre binaire pour diminuer sa hauteur.
Dictionnaires Un arbre binaire de recherche peut représenter un dictionnaire. Les étiquettes sont les paires clé-valeur et l'ordre des étiquettes est l'ordre des clés.
Pour faire l'opération put, on descend dans l'arbre à la recherche de la clé fournie. Si on trouve la clé, on remplace sa valeur. Si on ne la trouve pas, on ajoute un nœud là où la recherche s'est arrêtée.
Pour faire get, on recherche la clé et on a immédiatement notre réponse.
Pour faire remove, on recherche la clé.
- Si le nœud est une feuille, on le retire directement.
- Si le nœud a un enfant, il est remplacé par celui-ci.
- Si le nœud a deux enfants, on le remplace par son successeur directe (qui est retiré d'abord). Il faut trouver le successeur direct à la racine que l'on souhaite supprimé, en allant une fois à droite puis au plus à gauche possible.
Exemple :
public class ArbreDeRecherche<T extends Comparable<T>> {
private class Noeud {
T valeur;
Noeud gauche, droite;
Noeud(T valeur) {
this.valeur = valeur;
}
}
private Noeud racine;
public void remove(T valeur) {
racine = supprimerRecursif(racine, valeur);
}
private Noeud supprimerRecursif(Noeud actuel, T valeur) {
if (actuel == null) return null;
int comparaison = valeur.compareTo(actuel.valeur);
if (comparaison < 0) {
// On cherche à gauche
actuel.gauche = supprimerRecursif(actuel.gauche, valeur);
} else if (comparaison > 0) {
// On cherche à droite
actuel.droite = supprimerRecursif(actuel.droite, valeur);
} else {
// ÉLÉMENT TROUVÉ : On applique les 3 cas de suppression
// Cas 1 & 2 : Pas d'enfant ou un seul enfant
if (actuel.gauche == null) return actuel.droite;
if (actuel.droite == null) return actuel.gauche;
// Cas 3 : Deux enfants
// 1. Trouver le successeur (le plus petit du sous-arbre droit)
actuel.valeur = trouverSuccesseur(actuel.droite);
// 2. Supprimer le successeur dans le sous-arbre droit
actuel.droite = supprimerRecursif(actuel.droite, actuel.valeur);
}
return actuel;
}
private T trouverSuccesseur(Noeud racine) {
T valeurMin = racine.valeur;
// On descend au plus profond à gauche avec une boucle while
while (racine.gauche != null) {
valeurMin = racine.gauche.valeur;
racine = racine.gauche;
}
return valeurMin;
}
}