Comment utiliser cette page

Cette page suit toujours le même plan : le vocabulaire, la définition, un exemple unique (repris dans toute la page pour ne pas avoir à en retenir plusieurs), l'implémentation en Python avec une classe Noeud, la hauteur, la taille, un aperçu du codage avec un arbre, puis la complexité.

Utilisation de la démonstration
La démonstration avance uniquement quand vous cliquez sur « Suivant » ou « Précédent » : rien ne bouge tout seul. Un bouton « Lecture automatique » est disponible si vous préférez laisser les étapes défiler seules, à une vitesse que vous choisissez.
📄 Le code de cette page, en fichier téléchargeable
Le fichier src/arbres_binaires.py, dans le sous-dossier src à côté de cette page, contient tout le code présenté ici (classe Noeud, hauteur, taille, décodage). Après l'avoir téléchargé, on l'exécute dans un terminal, dans le dossier où il a été enregistré, avec python3 arbres_binaires.py (Python doit être installé sur l'ordinateur). Le fichier affiche « OK » pour chaque test s'il n'y a pas d'erreur.

1. Vocabulaire à connaître avant de commencer

Chaque mot ci-dessous est utilisé exactement dans ce sens dans toute la page.

MotDéfinition précise
ArbreUne structure de données hiérarchique : soit elle est vide, soit elle est formée d'un élément (un nœud) auquel sont reliés un ou plusieurs sous-arbres.
NœudChaque élément de l'arbre. Un nœud contient une valeur, et il est relié à d'autres nœuds.
RacineLe nœud tout en haut de l'arbre, celui qui n'a pas de père. C'est le point de départ pour parcourir tout l'arbre.
BrancheLe trait qui relie deux nœuds entre eux.
PèrePour un nœud donné, le nœud juste au-dessus de lui, auquel il est directement relié.
FilsPour un nœud donné, un nœud directement relié en dessous de lui. Un nœud peut avoir plusieurs fils.
FeuilleUn nœud qui n'a aucun fils : c'est un nœud terminal, tout en bas d'une branche.
Arbre n-aireUn arbre où chaque nœud a au plus n fils.
Arbre binaireUn arbre n-aire avec n = 2 : chaque nœud a donc au plus 2 fils. C'est le seul type d'arbre étudié dans cette fiche.
Sous-arbre gauche / droitDans un arbre binaire, le premier fils d'un nœud (et tout ce qui est relié en dessous de lui) est appelé sous-arbre gauche ; le second, sous-arbre droit.
Arbre videUn arbre qui ne contient aucun nœud. Dans les diagrammes de cette page, un arbre vide est représenté par une case en pointillés portant le mot « vide ».
HauteurLe nombre maximum de branches entre la racine et la feuille la plus basse. Par convention, la hauteur d'un arbre vide est -1.
TailleLe nombre total de nœuds de l'arbre.

2. Qu'est-ce qu'un arbre ? Définition récursive

Beaucoup d'informations peuvent être organisées sous la forme d'un arbre : un arbre généalogique, une arborescence de fichiers sur un ordinateur, ou encore la structure d'une phrase. Voici un exemple simple, avec un nœud racine qui a trois fils :

  • Racine
    • Fils 1
    • Fils 2
    • Fils 3

Un arbre avec une racine et 3 fils : c'est un exemple d'arbre n-aire (ici, chaque nœud peut avoir jusqu'à 3 fils). « Fils 1 », « Fils 2 » et « Fils 3 » sont ici tous des feuilles, car ils n'ont eux-mêmes aucun fils.

Définition récursive d'un arbre
Un arbre est :
  1. soit vide,
  2. soit constitué d'un élément (un nœud) auquel sont reliés un ou plusieurs sous-arbres.
Cette définition est récursive : un sous-arbre est lui-même défini par les deux mêmes règles. C'est pour cela que les algorithmes qui manipulent des arbres sont, presque toujours, des fonctions récursives.

3. L'arbre binaire : le cas particulier étudié dans cette fiche

Un arbre binaire est un arbre n-aire avec n = 2 : chaque nœud a donc au plus deux fils, appelés le fils gauche et le fils droit. Le sous-arbre qui part du fils gauche est appelé le sous-arbre gauche ; celui qui part du fils droit, le sous-arbre droit.

Définition récursive d'un arbre binaire
Un arbre binaire est :
  1. soit vide,
  2. soit un nœud auquel sont reliés un sous-arbre gauche (qui est lui-même un arbre binaire) et un sous-arbre droit (qui est lui-même un arbre binaire).
Un sous-arbre gauche ou droit peut tout à fait être vide : un nœud qui n'a qu'un seul fils a donc quand même deux sous-arbres, l'un des deux étant vide. C'est pour cela que, dans cette page, chaque nœud est toujours dessiné avec exactement deux cases en dessous de lui : une case grise pour un sous-arbre qui existe, ou une case en pointillés « vide » pour un sous-arbre vide.
Remarque
Tout sous-arbre d'un arbre binaire est lui-même un arbre binaire. C'est cette propriété qui permet d'écrire des algorithmes récursifs sur les arbres binaires : un algorithme qui fonctionne sur un arbre binaire fonctionne, sans rien changer, sur chacun de ses sous-arbres.

4. L'exemple utilisé dans toute la suite de cette page

Le même arbre binaire, avec 8 nœuds nommés A, G, D, Gp, O, P, Dg et Dd, est repris dans toutes les sections qui suivent : construction avec la classe Noeud, calcul de la hauteur, puis calcul de la taille.

NœudPèreFils gaucheFils droitFeuille ?
A (racine)— (aucun)GDnon
GAGpvidenon
DADgDdnon
GpGOPnon
OGpvidevideoui
PGpvidevideoui
DgDvidevideoui
DdDvidevideoui

Cet arbre n'est pas symétrique : sa branche gauche (A → G → Gp → O ou P) descend d'un niveau de plus que sa branche droite (A → D → Dg ou Dd). Ce choix est volontaire : il permet de bien voir, plus loin, que la hauteur d'un arbre dépend de sa branche la plus profonde, pas de la moyenne de ses branches.

5. Implémentation en Python avec une classe Noeud

5.1 — Les opérations primitives

OpérationEffet
creer_arbre()crée un arbre binaire vide
creer_arbre_racine(v)crée un arbre binaire dont la racine vaut v, avec deux sous-arbres vides
est_vide(A)renvoie True si l'arbre A est vide, sinon False
racine(A)renvoie la valeur de la racine de A
sous_arbre_gauche(A)renvoie le sous-arbre gauche de A
sous_arbre_droit(A)renvoie le sous-arbre droit de A

5.2 — La classe Noeud

On représente un arbre binaire avec une classe Noeud : chaque objet Noeud a un attribut racine (la valeur du nœud), un attribut gauche (le sous-arbre gauche, ou None s'il est vide) et un attribut droit (le sous-arbre droit, ou None s'il est vide).

class Noeud:
    def __init__(self, racine):
        self.gauche = None
        self.racine = racine
        self.droit = None

    def __repr__(self):
        return f'{self.racine}({str(self.gauche)},{str(self.droit)})'

5.3 — Construire pas à pas l'arbre de l'exemple

Principe, en une phrase
On crée d'abord les nœuds les plus profonds (les feuilles), puis on remonte niveau par niveau en reliant chaque nœud déjà créé au sous-arbre gauche ou droit de son père, jusqu'à obtenir l'arbre complet en partant de la racine A.
  1. Créer les feuilles O et P.
  2. Créer Gp, puis relier O comme son fils gauche et P comme son fils droit.
  3. Créer G, puis relier Gp comme son fils gauche (son fils droit reste vide, None).
  4. Créer les feuilles Dg et Dd.
  5. Créer D, puis relier Dg comme son fils gauche et Dd comme son fils droit.
  6. Créer la racine A, puis relier G comme son fils gauche et D comme son fils droit.
nœud ou lien qui vient d'être créé sous-arbre vide (None)
Étape 1 / 1
La ligne de code correspondant à l'étape en cours est surlignée ci-dessous. Avant que la racine A n'existe, les fragments d'arbre déjà créés sont affichés côte à côte : ils ne sont pas encore reliés entre eux.
O = Noeud('O')P = Noeud('P')Gp = Noeud('Gp')Gp.gauche = OGp.droit = PG = Noeud('G')G.gauche = Gp          # G.droit reste vide (None)Dg = Noeud('Dg')Dd = Noeud('Dd')D = Noeud('D')D.gauche = DgD.droit = DdA = Noeud('A')A.gauche = GA.droit = D

Pour se donner une idée de l'arbre construit, on peut afficher A directement (c'est la méthode __repr__ qui est invoquée) :

>>> A
A(G(Gp(O(None,None),P(None,None)),None),D(Dg(None,None),Dd(None,None)))

On retrouve, en le lisant, exactement la notation parenthésée de l'arbre : chaque nœud est suivi de ses deux sous-arbres entre parenthèses, dans l'ordre gauche puis droit, et None représente un sous-arbre vide.

6. La hauteur d'un arbre binaire

Définition récursive de la hauteur
  1. Par convention, la hauteur d'un arbre binaire vide est -1.
  2. La hauteur d'un arbre binaire non vide est égale à 1 + le maximum entre la hauteur de son sous-arbre gauche et la hauteur de son sous-arbre droit.

6.1 — Trace pas à pas sur l'exemple

On calcule la hauteur en partant des feuilles (les plus faciles, car leurs deux sous-arbres sont vides) et en remontant vers la racine A.

Nœudhauteur(sous-arbre gauche)hauteur(sous-arbre droit)hauteur du nœud = 1 + max(gauche, droit)
O (feuille)-1 (vide)-1 (vide)1 + max(-1, -1) = 0
P (feuille)-1 (vide)-1 (vide)1 + max(-1, -1) = 0
Gp0 (hauteur de O)0 (hauteur de P)1 + max(0, 0) = 1
G1 (hauteur de Gp)-1 (vide)1 + max(1, -1) = 2
Dg (feuille)-1 (vide)-1 (vide)1 + max(-1, -1) = 0
Dd (feuille)-1 (vide)-1 (vide)1 + max(-1, -1) = 0
D0 (hauteur de Dg)0 (hauteur de Dd)1 + max(0, 0) = 1
A (racine)2 (hauteur de G)1 (hauteur de D)1 + max(2, 1) = 3

La hauteur de l'arbre complet, hauteur(A), vaut donc 3.

La hauteur de chaque nœud est indiquée par le petit badge vert. On voit que le nœud G vaut 2 (et pas 1 + hauteur de Gp + 1) : c'est bien 1 + max(hauteur(Gp), hauteur(vide)) = 1 + max(1, -1) = 2.

6.2 — Le code Python

def hauteur(self):
    if self is None:
        return -1
    else:
        hauteur_gauche = self.gauche.hauteur() if self.gauche else -1
        hauteur_droit = self.droit.hauteur() if self.droit else -1
        return 1 + max(hauteur_gauche, hauteur_droit)
Pourquoi if self is None ne se déclenche-t-il jamais ici ?
Cette méthode est appelée sur un objet Noeud, qui n'est donc jamais None lui-même : le test if self is None protège en réalité les appels récursifs. Comme self.gauche et self.droit peuvent valoir None (sous-arbre vide), on n'appelle .hauteur() dessus que s'ils existent (if self.gauche else -1) ; sinon, on utilise directement -1, la hauteur d'un arbre vide.

7. La taille d'un arbre binaire

Définition récursive de la taille
  1. La taille d'un arbre binaire vide est 0.
  2. La taille d'un arbre binaire non vide est égale à 1 + la taille de son sous-arbre gauche + la taille de son sous-arbre droit.

7.1 — Trace pas à pas sur le même exemple

Nœudtaille(sous-arbre gauche)taille(sous-arbre droit)taille du nœud = 1 + gauche + droit
O (feuille)0 (vide)0 (vide)1 + 0 + 0 = 1
P (feuille)0 (vide)0 (vide)1 + 0 + 0 = 1
Gp1 (taille de O)1 (taille de P)1 + 1 + 1 = 3
G3 (taille de Gp)0 (vide)1 + 3 + 0 = 4
Dg (feuille)0 (vide)0 (vide)1 + 0 + 0 = 1
Dd (feuille)0 (vide)0 (vide)1 + 0 + 0 = 1
D1 (taille de Dg)1 (taille de Dd)1 + 1 + 1 = 3
A (racine)4 (taille de G)3 (taille de D)1 + 4 + 3 = 8

La taille de l'arbre complet, taille(A), vaut donc 8 : c'est bien le nombre total de nœuds (A, G, D, Gp, O, P, Dg, Dd).

7.2 — Le code Python

def taille(self):
    if self is None:
        return 0
    else:
        taille_gauche = self.gauche.taille() if self.gauche else 0
        taille_droit = self.droit.taille() if self.droit else 0
        return 1 + taille_gauche + taille_droit

Cette méthode a exactement la même structure que hauteur : un cas de base pour l'arbre vide (0 au lieu de -1), et un appel récursif sur chaque sous-arbre existant.

8. Aperçu : coder des éléments avec un arbre binaire

On peut utiliser la structure d'un arbre binaire pour associer un code fait de 0 et de 1 à chaque feuille : « 0 » quand on emprunte le sous-arbre gauche, « 1 » quand on emprunte le sous-arbre droit. C'est ce principe, avec des arbres plus grands et plus élaborés, qui sera étudié en détail avec le codage de Huffman dans la séquence 9. Voici une version simplifiée, avec 4 fruits codés sur 2 bits chacun :

FruitChemin depuis la racineCode
🍎 Pommegauche, gauche00
🍐 Poiregauche, droite01
🍌 Bananedroite, gauche10
🍊 Orangedroite, droite11
Principe du décodage, en une phrase
On part de la racine, on lit le message bit par bit : à chaque « 0 » on descend dans le sous-arbre gauche, à chaque « 1 » on descend dans le sous-arbre droit ; dès qu'on atteint une feuille, elle donne le fruit décodé, et on repart de la racine pour le groupe de bits suivant.

8.1 — Trace pas à pas du décodage de "01100011"

Bit luDirectionNœud atteintFeuille ? Fruit décodé
0gauchenœud interne gauchenon
1droitePoireoui → Poire ; on repart de la racine
1droitenœud interne droitnon
0gaucheBananeoui → Banane ; on repart de la racine
0gauchenœud interne gauchenon
0gauchePommeoui → Pomme ; on repart de la racine
1droitenœud interne droitnon
1droiteOrangeoui → Orange ; fin du message

Le message décodé est donc : Poire, Banane, Pomme, Orange.

8.2 — Le code Python

def decoder(arbre, code):
    """Décode une chaîne de 0 et de 1 en suivant l'arbre, et renvoie
    la liste des feuilles rencontrées (dans l'ordre)."""
    resultat = []
    noeud = arbre
    for bit in code:
        noeud = noeud.gauche if bit == '0' else noeud.droit
        if noeud.gauche is None and noeud.droit is None:
            # noeud est une feuille : on a fini un symbole
            resultat.append(noeud.racine)
            noeud = arbre
    return resultat
Différence avec un vrai codage de Huffman
Ici, les 4 fruits ont tous un code de la même longueur (2 bits), car l'arbre est parfaitement équilibré. Le codage de Huffman, lui, utilise un arbre déséquilibré exprès : les symboles les plus fréquents sont placés plus haut, avec un code plus court ; les symboles rares sont placés plus bas, avec un code plus long. Ce point sera développé dans la séquence 9.

9. Récapitulatif des complexités

OpérationComplexitéPourquoi
hauteur(A)O(n)l'appel récursif visite chacun des n nœuds de l'arbre exactement une fois
taille(A)O(n)même raison : chaque nœud est visité une seule fois
decoder(arbre, code)O(longueur du code)chaque bit du code fait descendre le parcours d'un seul niveau dans l'arbre
creer_arbre_racine(v), racine(A), est_vide(A)O(1)ce sont de simples accès à un attribut de l'objet Noeud, sans parcourir l'arbre

n désigne ici le nombre de nœuds de l'arbre (sa taille). Un arbre binaire de n nœuds a une hauteur minimale égale au nombre de chiffres de n écrit en base 2 (par exemple, un arbre de 197 nœuds a une hauteur minimale de 8, car 197 s'écrit avec 8 chiffres en base deux) ; sa hauteur peut cependant être bien plus grande si l'arbre n'est pas équilibré.

10. Points clés à retenir

Prêt(e) pour la suite ? → Retour à l'accueil