Comment utiliser cette page

Cette page est un exercice complet, résolu pas à pas : l'énoncé, l'analyse du problème, la solution en Python, une trace détaillée sur l'exemple déjà connu de la fiche « Les arbres binaires », puis une vérification et deux exercices supplémentaires à faire seul(e).

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_exercice.py, dans le sous-dossier src à côté de cette page, contient la classe Noeud, la méthode compter_feuilles, et tous les tests. 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_exercice.py (Python doit être installé sur l'ordinateur). Le fichier affiche « OK » pour chaque test s'il n'y a pas d'erreur.

Énoncé de l'exercice

Énoncé
On dispose d'un arbre binaire construit avec la classe Noeud (attributs gauche, racine, droit), comme dans la fiche « Les arbres binaires ». Écrire une méthode compter_feuilles qui renvoie le nombre de feuilles de l'arbre, c'est-à-dire le nombre de nœuds qui n'ont ni fils gauche ni fils droit.

On reprend exactement le même arbre que dans la fiche « Les arbres binaires », avec ses 8 nœuds A, G, D, Gp, O, P, Dg, Dd :

Rappel : une feuille est un nœud dont les deux sous-arbres (gauche et droit) sont vides. Dans cet arbre, les feuilles sont O, P, Dg et Dd : il y en a donc 4.

1. Analyser le problème avant d'écrire du code

Principe, en une phrase
Pour compter les feuilles d'un arbre binaire, il faut distinguer trois situations différentes pour un nœud donné : l'arbre est vide, le nœud est lui-même une feuille, ou le nœud a au moins un fils (et il faut alors additionner les feuilles trouvées à gauche et à droite).

Dans les méthodes hauteur et taille de la fiche « Les arbres binaires », il suffisait de distinguer deux cas : l'arbre vide, et l'arbre non vide (avec un appel récursif de chaque côté). Ici, deux cas ne suffisent pas.

  1. Cas 1 — l'arbre est vide (self vaut None) : il ne contient aucune feuille, donc compter_feuilles doit renvoyer 0.
  2. Cas 2 — le nœud est une feuille (son gauche et son droit valent tous les deux None) : ce nœud compte à lui seul pour une feuille, donc compter_feuilles doit renvoyer 1, sans continuer à descendre dans l'arbre (il n'y a plus rien en dessous).
  3. Cas 3 — le nœud a au moins un fils : ce n'est pas une feuille, donc il ne compte pas pour 1 lui-même ; le nombre de feuilles est la somme du nombre de feuilles du sous-arbre gauche et du nombre de feuilles du sous-arbre droit.
Pourquoi le cas 2 est indispensable
Si on essayait d'adapter directement la formule de taille (« 1 + taille du sous-arbre gauche + taille du sous-arbre droit »), on obtiendrait pour une feuille comme O : compter_feuilles(O) = compter_feuilles(vide) + compter_feuilles(vide) = 0 + 0 = 0. C'est faux : O est bien une feuille, la réponse doit être 1. C'est pour cela qu'il faut un cas à part, testé avant de descendre récursivement dans les sous-arbres : « ce nœud est-il lui-même une feuille ? ».

2. Écrire la solution

def compter_feuilles(self):    if self is None:        return 0    elif self.gauche is None and self.droit is None:        return 1    else:        feuilles_gauche = self.gauche.compter_feuilles() if self.gauche else 0        feuilles_droit = self.droit.compter_feuilles() if self.droit else 0        return feuilles_gauche + feuilles_droit

Cette méthode a exactement la même forme que hauteur et taille : un test sur self is None (qui protège les appels récursifs faits via self.gauche.compter_feuilles() if self.gauche else 0), puis un calcul qui combine les résultats des deux sous-arbres. La seule différence est le cas supplémentaire elif, ajouté avant ce calcul, pour arrêter la récursion dès qu'on atteint une feuille.

3. Trace pas à pas sur l'exemple

nœud en cours de calcul
Étape 1 / 1
On calcule d'abord les nœuds les plus profonds, puis on remonte vers la racine A. La ligne de code correspondant au cas utilisé est surlignée ci-dessus.
NœudCasCalculcompter_feuilles
Ofeuille—1
Pfeuille—1
Gpa des fils (gauche et droit)1 (O) + 1 (P)2
Ga un fils (gauche seulement)2 (Gp) + 0 (vide)2
Dgfeuille—1
Ddfeuille—1
Da des fils (gauche et droit)1 (Dg) + 1 (Dd)2
A (racine)a des fils (gauche et droit)2 (G) + 2 (D)4

compter_feuilles(A) vaut donc 4 : c'est bien le nombre de feuilles de l'arbre (O, P, Dg, Dd).

4. Vérifier avec Python

A = construire_arbre_exemple()   # construit A, G, D, Gp, O, P, Dg, Dd comme dans la fiche « Les arbres binaires »

assert A.compter_feuilles() == 4
assert A.gauche.compter_feuilles() == 2       # G
assert A.droit.compter_feuilles() == 2        # D
assert A.gauche.gauche.compter_feuilles() == 2   # Gp
assert A.gauche.gauche.gauche.compter_feuilles() == 1   # O (feuille)

On peut aussi vérifier une propriété utile : dans un arbre binaire, taille(A) = compter_feuilles(A) + nombre de nœuds internes. Ici, taille(A) = 8 (vu dans la fiche « Les arbres binaires ») et compter_feuilles(A) = 4, donc l'arbre a aussi 4 nœuds internes (A, G, D, Gp) — ce qui se vérifie bien sur le diagramme.

5. Complexité

compter_feuilles est en O(n), où n est le nombre de nœuds de l'arbre : comme pour hauteur et taille, l'appel récursif visite chaque nœud exactement une fois, et fait un travail constant à chaque nœud (un test, puis une addition).

6. Pour s'entraîner

En reprenant exactement la même démarche (analyser les cas, écrire la fonction récursive, tracer sur l'exemple dans un tableau), essayer de résoudre ces deux exercices sur le même arbre A :

  1. Compter les nœuds internes : écrire une méthode compter_noeuds_internes qui renvoie le nombre de nœuds ayant au moins un fils. (Indication : le résultat doit être cohérent avec taille(A) = compter_feuilles(A) + compter_noeuds_internes(A), soit 8 = 4 + 4.)
  2. Profondeur maximale à gauche : écrire une méthode tout_a_gauche qui renvoie la valeur de la racine du dernier nœud atteint en descendant uniquement par les fils gauche, en partant de la racine, jusqu'à un nœud qui n'a pas de fils gauche. (Sur l'arbre A, la réponse attendue est 'O'.)
Revoir le cours en détail ? → Fiche « Les arbres binaires » · Retour à l'accueil