cours1 min de lecture
Structures hiérarchiques — vocabulaire, mesures, encadrement de la hauteur, classe Noeud en Python.
programme

Introduction

Quand les données ont une hiérarchie naturelle — un système de fichiers, un arbre généalogique, le DOM d'une page web, la décomposition syntaxique d'une expression — les structures linéaires ne suffisent plus. On utilise alors un arbre : un ensemble de nœuds organisés autour d'une racine, où chaque nœud porte un nombre fini d'enfants.

L'image la plus familière est l'arbre généalogique : une personne en haut, ses descendants qui se ramifient à mesure qu'on descend. La racine informatique est en haut par convention — les arbres en informatique poussent vers le bas, contrairement à leurs cousins botaniques.

Ce cours traite spécifiquement des arbres binaires (chaque nœud a au plus deux enfants), conformément à . Les algorithmes associés (parcours préfixe, infixe, suffixe, BFS, DFS) sont abordés au chapitre TE.

Vocabulaire

TermeDéfinition
NœudÉlément de l'arbre, portant une valeur.
RacineNœud sans parent — point d'entrée unique de l'arbre.
FeuilleNœud sans enfant.
Sous-arbre gaucheArbre enraciné au fils gauche d'un nœud.
Sous-arbre droitArbre enraciné au fils droit.
ArêteLien entre un parent et un de ses enfants.
Taille nnNombre total de nœuds de l'arbre.
Hauteur hhLongueur (en arêtes) du plus long chemin de la racine à une feuille.
Profondeur d'un nœudLongueur du chemin de la racine à ce nœud.
Attention aux conventions de hauteur. Certains auteurs comptent en nombre de niveaux (racine = niveau 1) ; d'autres en arêtes (racine = hauteur 0). Nous adoptons ici la convention en arêtes : un arbre réduit à sa racine a une hauteur h=0h = 0, une taille n=1n = 1.

Implémentation Python

On reprend l'outil objet du cours précédent. Un nœud est un objet portant une valeur, un fils gauche et un fils droit (qui sont eux-mêmes des nœuds ou None).

class Noeud:
    """Nœud d'un arbre binaire."""

    def __init__(self, valeur, gauche=None, droite=None):
        self.valeur = valeur
        self.gauche = gauche
        self.droite = droite

    def est_feuille(self):
        return self.gauche is None and self.droite is None

Création d'un petit arbre :

#       1
#      / \
#     2   3
#    /
#   4
arbre = Noeud(1, Noeud(2, Noeud(4)), Noeud(3))

Dans l'arbre ci-dessus, combien de feuilles ?

Mesurer un arbre binaire

Taille

Le nombre total de nœuds, défini récursivement :

taille(A)={0si A est vide1+taille(Ag)+taille(Ad)sinon\mathrm{taille}(A) = \begin{cases} 0 & \text{si } A \text{ est vide} \\ 1 + \mathrm{taille}(A_g) + \mathrm{taille}(A_d) & \text{sinon} \end{cases}
def taille(noeud):
    if noeud is None:
        return 0
    return 1 + taille(noeud.gauche) + taille(noeud.droite)

Hauteur

hauteur(A)={1si A est vide1+max(hauteur(Ag),hauteur(Ad))sinon\mathrm{hauteur}(A) = \begin{cases} -1 & \text{si } A \text{ est vide} \\ 1 + \max(\mathrm{hauteur}(A_g), \mathrm{hauteur}(A_d)) & \text{sinon} \end{cases}

La convention hauteur()=1\mathrm{hauteur}(\emptyset) = -1 rend la formule cohérente : un arbre à un seul nœud a alors une hauteur 1+max(1,1)=01 + \max(-1, -1) = 0.

def hauteur(noeud):
    if noeud is None:
        return -1
    return 1 + max(hauteur(noeud.gauche), hauteur(noeud.droite))

Encadrement de la hauteur

Question canonique du BO : pour un arbre binaire de taille nn et de hauteur hh, peut-on borner hh en fonction de nn ?

Théorème

Pour tout arbre binaire non vide de taille nn et de hauteur hh :

log2(n+1)1hn1\log_2(n+1) - 1 \le h \le n - 1

de manière équivalente :

h+1n2h+11h + 1 \le n \le 2^{h+1} - 1

Preuve par récurrence

Montrons : un arbre binaire de hauteur hh vérifie h+1n2h+11h + 1 \le n \le 2^{h+1} - 1.

Initialisation (h=0h = 0). L'arbre est réduit à sa racine. On a n=1n = 1, et h+1=1h + 1 = 1, et 2h+11=12^{h+1} - 1 = 1. L'inégalité 1111 \le 1 \le 1 tient.

Hérédité. Supposons la propriété vraie pour toute hauteur strictement inférieure à hh (avec h1h \ge 1). Soit AA un arbre binaire de hauteur hh. Ses sous-arbres AgA_g et AdA_d ont chacun une hauteur h1\le h - 1, et au moins l'un des deux atteint h1h - 1 (sinon AA aurait une hauteur strictement inférieure à hh).

  • Minoration. Le sous-arbre qui atteint h1h - 1 contient au moins (h1)+1=h(h - 1) + 1 = h nœuds par hypothèse de récurrence. Avec la racine de AA, on a donc n1+h=h+1n \ge 1 + h = h + 1.
  • Majoration. Chaque sous-arbre a au plus 2h12^{h} - 1 nœuds par hypothèse de récurrence. Donc n1+2(2h1)=2h+11.n \le 1 + 2(2^{h} - 1) = 2^{h+1} - 1.

L'inégalité est démontrée pour la hauteur hh. Par récurrence, elle est vraie pour tout h0h \ge 0. Par passage au logarithme dans la majoration n2h+11n \le 2^{h+1} - 1, on obtient hlog2(n+1)1h \ge \log_2(n+1) - 1, ce qui ferme l'équivalence annoncée. \blacksquare

Conséquences

  • Un arbre filiforme (chaque nœud n'a qu'un seul enfant) atteint la borne basse : n=h+1n = h + 1, donc h=n1h = n - 1.
  • Un arbre parfaitement équilibré et complet atteint la borne haute : n=2h+11n = 2^{h+1} - 1, donc h=log2(n+1)1h = \log_2(n+1) - 1, soit hO(logn)h \in \mathcal{O}(\log n).
  • C'est pour cela que les structures arborescentes équilibrées sont recherchées : leurs opérations sont en O(logn)\mathcal{O}(\log n) au lieu de O(n)\mathcal{O}(n).

Un arbre binaire de hauteur h=3h = 3h=3 contient au plus combien de nœuds ?

Pour aller plus loin

Les arbres binaires de recherche (ABR), les tas binaires et les arbres équilibrés (AVL, rouge-noir) exploitent cette propriété d'encadrement pour garantir des opérations en O(logn)\mathcal{O}(\log n). Vous reviendrez sur certains d'entre eux au chapitre TE.