Aller au contenu principal

Arbres

TAD et structures de données35 min

Hiérarchies, arbre binaire et recherche guidée par un invariant.

Dans cette leçon

Reconnaître une hiérarchie

Un arbre convient quand une donnée contient des sous-parties, qui contiennent elles-mêmes des sous-parties. Il exprime une hiérarchie, là où une liste exprime surtout une suite.

Pensez à un système de fichiers, un menu, des catégories de produits ou des commentaires imbriqués. Chaque élément peut porter une valeur et mener vers plusieurs éléments placés en dessous. Il n'existe pas forcément d'index utile pour atteindre « le troisième élément » : le chemin dans la hiérarchie donne le sens.

Un arbre, concrètement

Un arbre n'est rien de plus que des nœuds reliés entre eux. Chaque nœud contient une valeur et des liens vers ses enfants. Chaque enfant est à son tour la racine d'un arbre plus petit.

Les données ressemblent à…RéflexePourquoi
Une suite ordonnéeListeLes positions et le voisin suivant dominent.
Des niveaux et des sous-partiesArbreChaque nœud ouvre un sous-arbre.
Un réseau avec plusieurs routesGrapheUn parent unique ne suffit plus.

Lire un arbre sans ambiguïté

Le vocabulaire décrit une position ou une relation. Comptez toujours des arêtes, pas des nœuds, pour la profondeur et la hauteur.

TermeDéfinitionDans le dessin précédent
NœudUn élément qui porte une valeur.src
ArêteLe lien entre un parent et un enfant.projet → docs
RacineLe seul nœud sans parent.projet
FeuilleUn nœud qui n'a aucun enfant.App.java
ProfondeurLe nombre d'arêtes depuis la racine.App.java : 2
HauteurLe plus long chemin vers une feuille en dessous.src : 1
Sous-arbreUn nœud et tout ce qui descend de lui.src + ses fichiers

La question qui évite les confusions

Pour la profondeur, demandez « à combien d'arêtes suis-je de la racine ? ». Pour la hauteur, demandez « quel est le plus long chemin qui reste jusqu'à une feuille ? ».

L'arbre général : une liste d'enfants

Dans un arbre général, chaque nœud possède une liste d'enfants. Cette liste peut être vide ou contenir autant d'éléments que le domaine le demande.

TreeNode.java
  1. Le cas local est simple

    Affichez la valeur du nœud courant. Une opération sur un arbre commence souvent par ce petit travail local.
  2. Chaque enfant pose le même problème

    Demandez ensuite à chaque enfant de s'afficher avec une indentation supplémentaire. Le sous-arbre a exactement la même forme que l'arbre entier.
  3. La liste vide arrête naturellement

    Une feuille n'a aucun enfant. La boucle ne lance donc aucun nouvel appel et la descente s'arrête.

Le bon modèle mental

Ne cherchez pas à mémoriser toute la hiérarchie pendant l'appel. Chaque exécution s'occupe d'un nœud, puis confie chaque sous-arbre à la même méthode. L'annexe sur la récursivité détaille ce mécanisme.

Un sous-arbre est encore un arbre

Chaque enfant est la racine d'un sous-arbre. Chercher dans l'arbre revient donc à chercher de la même façon dans chacun de ces arbres plus petits. Cette répétition du même problème est la récursivité.

  1. Arrêtez une branche vide

    Si le nœud vaut null, il n'y a plus rien à chercher, compter ou additionner. C'est le cas de base.
  2. Traitez le nœud courant

    Comparez sa valeur, comptez-la ou utilisez-la dans le résultat. Chaque appel ne s'occupe que de ce travail local.
  3. Confiez les enfants à la même méthode

    Appelez la méthode sur chaque enfant. Ces appels appliquent exactement les mêmes trois étapes à des sous-arbres plus petits.
  4. Combinez les réponses

    Une recherche s'arrête au premier enfant qui répond true. Un comptage ou une somme ajoute le résultat de chaque enfant.
NumberTreeNode.java

Exemple 1 : chercher une valeur partout

ContainsAnywhere.java

Le nœud courant répond d'abord. Sinon, la même recherche part dans le sous-arbre de chaque enfant. Dès qu'une branche trouve la valeur, la méthode retourne true sans examiner les enfants restants.

Exemple 2 : compter les valeurs impaires

CountOddValues.java

Chaque appel compte zéro ou un pour son propre nœud, puis ajoute le compte de chacun de ses enfants. Une branche vide contribue zéro.

Exemple 3 : additionner toutes les valeurs

SumValues.java

Seule l'opération locale change. La structure récursive reste la même : cas de base, valeur du nœud courant, appel sur chaque enfant, combinaison des résultats.

OpérationBranche videNœud courantCombinaison
ChercherfalseComparer au targetTester chaque enfant jusqu'au premier true
Compter les impairs0Ajouter 1 si impairAjouter le compte de chaque enfant
Additionner0Prendre la valeurAjouter la somme de chaque enfant

L'arbre est l'un des usages les plus courants de la récursivité

La forme de la donnée et la forme du code se répondent : un arbre contient des sous-arbres, donc la méthode s'appelle elle-même sur ces sous-arbres. Consultez maintenant l'annexe Récursivité pour maîtriser le cas de base et la réduction du problème.

Un arbre binaire n'est pas forcément trié

Un arbre binaire remplace la liste d'enfants par deux références nommées : left et right. Chaque nœud a donc zéro, un ou deux enfants. C'est une contrainte de forme, rien de plus.

BinaryNode.java

Binaire ne veut pas dire trié

Le dessin respecte la limite de deux enfants, mais 12 est à gauche de 8 et 3 à droite. Rien ne permet de choisir une seule direction pour chercher une valeur. Il faut potentiellement regarder des deux côtés.

La boucle sur les enfants montrée plus haut devient ici deux liens nommés. Une recherche générale appelle la même méthode sur left puis right. Sans invariant d'ordre, aucun des deux côtés ne peut être éliminé.

Le BST ajoute un invariant

Un arbre binaire de recherche est un arbre binaire auquel on ajoute une règle d'ordre : toutes les clés du sous-arbre gauche sont plus petites, toutes celles du sous-arbre droit sont plus grandes, et cette règle reste vraie dans chaque sous-arbre.

BstNode.java

Arbre général

  • Autant d'enfants que nécessaire.
  • Exprime directement une hiérarchie libre.
  • Aucune direction n'est liée à la valeur.

Arbre binaire

  • Au plus deux enfants par nœud.
  • Les liens gauche et droite donnent une forme régulière.
  • Sans règle supplémentaire, la recherche reste non dirigée.

BST

  • Même forme binaire, plus un invariant d'ordre.
  • Une comparaison élimine un sous-arbre entier.
  • La clé peut piloter une structure clé-valeur.

Une comparaison choisit la direction

Cherchons 30 dans le BST précédent. La comparaison avec le nœud courant suffit à choisir le prochain lien. Avec des chaînes, compareTo donne cette décision : résultat négatif vers la gauche, positif vers la droite, zéro quand la clé est trouvée.

  1. Comparez 30 à 40

    30 est plus petit. Descendez uniquement dans le sous-arbre gauche. Le sous-arbre droit de 40 ne peut pas contenir 30 si l'invariant est respecté.
  2. Comparez 30 à 20

    30 est plus grand. Descendez uniquement à droite de 20.
  3. Comparez 30 à 30

    Les valeurs sont égales. La recherche se termine après trois comparaisons.
40 → comparaison 120 → comparaison 230 → trouvé

L'invariant est un contrat

La recherche dirigée est correcte seulement si chaque insertion a conservé la règle d'ordre. Une seule clé mal branchée peut devenir invisible à une méthode pourtant bien écrite.

La forme décide du nombre de comparaisons

L'invariant dit où chercher, mais la forme détermine combien de nœuds vous devrez comparer. L'ordre d'insertion peut produire deux arbres valides très différents.

Forme bien répartie

jusqu'à 3 comparaisons ici

Forme dégénérée

jusqu'à 5 comparaisons ici

La limite à retenir

Un BST déséquilibré reste correct mais perd le bénéfice de la recherche courte ; des structures auto-équilibrées comme AVL corrigent cette forme, mais leur étude dépasse cette remédiation.

Hors de cette leçon

Vous n'avez pas à mémoriser un catalogue de façons de visiter l'arbre, ni à coder la suppression. Ici, vous devez distinguer les formes, maintenir l'invariant et diriger une recherche.

Écrivez la recherche dirigée

Écrivez maintenant la méthode qui manque. Votre code doit exploiter l'invariant : après chaque comparaison, un seul appel récursif est nécessaire.

StudentTree.java

Recherche dirigée dans un BST

Écrivez d'abord. Une IA externe pourra ensuite relire votre travail. Son retour peut être faux. Vérifiez-le avec la leçon.

Complétez contains(Nœud nœud, String target). Votre méthode doit : - retourner false quand le nœud est null ; - utiliser target.compareTo(nœud.key) une seule fois par appel ; - retourner true quand la comparaison vaut zéro ; - descendre à gauche ou à droite, jamais des deux côtés ; - expliquer en une phrase pourquoi cette direction est sûre. Ajoutez ensuite une trace pour la recherche de "Lina" dans cet arbre : Mona à la racine, Hugo à gauche, Sara à droite, Lina à droite de Hugo.

Brouillon enregistré sur cet appareil.

À coller dans l'IA de votre choix.
Voir le prompt qui sera copié
Vous corrigez le travail d'un étudiant qui prépare une remédiation Java 17. Évaluez d'abord son raisonnement, ses invariants et la cohérence de son brouillon. Un code incomplet mais bien expliqué mérite un retour utile. Signalez précisément les erreurs sans inventer d'exigences hors sujet.

La leçon porte sur l'invariant d'un arbre binaire de recherche. Vérifiez surtout le cas de base, le sens de compareTo, le fait qu'un seul sous-arbre est exploré et la cohérence de la trace. N'exigez aucune autre technique d'arbre.

Travail demandé:
Complétez contains(Node node, String target).

Votre méthode doit :
- retourner false quand le nœud est null ;
- utiliser target.compareTo(node.key) une seule fois par appel ;
- retourner true quand la comparaison vaut zéro ;
- descendre à gauche ou à droite, jamais des deux côtés ;
- expliquer en une phrase pourquoi cette direction est sûre.

Ajoutez ensuite une trace pour la recherche de "Lina" dans cet arbre : Mona à la racine, Hugo à gauche, Sara à droite, Lina à droite de Hugo.

Commencez par poser au plus deux questions si une intention reste ambiguë. Donnez ensuite les points corrects, les problèmes précis et la plus petite prochaine étape. Ne fournissez une solution complète que si l'étudiant la demande explicitement.

Réponse de l'étudiant:
[aucune réponse collée]

Vérifiez vos réflexes

Vérifiez vos réflexes

Une réponse par question. La correction apparaît tout de suite.

1. Quelle propriété distingue un BST d'un simple arbre binaire ?
2. Quelle est la profondeur de la racine ?
3. Vous cherchez 30 et le nœud courant vaut 40 dans un BST de nombres. Que faites-vous ?
4. Pourquoi un BST construit avec 10, 20, 30, 40 peut-il devenir lent ?
5. Dans un arbre binaire sans invariant d'ordre, que doit faire une recherche générale après l'échec local ?

Continuez le parcours

La table de hachage et le BST offrent tous deux un accès par clé, mais leur organisation et leurs cas défavorables diffèrent. Pour consolider le mécanisme qui revient dans presque toutes les opérations sur un arbre, poursuivez avec la leçon sur la récursivité.

Centre de commandes

Rechercher une page et s'y rendre