Chapitre 6 : Les arbres binaires.

Introduction :

Et si l'on s'imposait une règle stricte : chaque élément ne peut mener qu'à deux autres au maximum, l'un à gauche, l'autre à droite ?

Cette contrainte peut sembler limitante, et pourtant elle ouvre des possibilités étonnantes. Comment parcourir une telle organisation des données ?

1. Définition

Un arbre binaire est un arbre dont l'arité est égale à deux.

Une fois ceci fixé, nous pouvons ainsi parler d'enfant gauche et d'enfant droit.

2. Exemple

James William Mary Charles Elizabeth Margaret Henry Alice Edward

Dans l'exemple ci-dessus :

3. Exercice : hauteur, taille et parenté dans un arbre binaire.

À faire dans le cahier.

Dans cet exercice un arbre composé uniquement d'un seul noeud a une hauteur de 1.

On considère cet arbre binaire :

Olivier Jack Emily Harry Sophie Mia George Charlie Isabelle Noah Ava
  1. Quelle est sa racine ?
  2. Quelle est sa hauteur ?
  3. Quelle est sa taille ?
  4. Combien a-t-il de feuilles ?
  5. Qui est l'enfant gauche de l'enfant gauche de l'enfant gauche du parent du parent de Mia ?

4. Définition

Dans un arbre binaire, à partir d'un noeud, on peut considérer son sous-arbre gauche et son sous-arbre droit.

5. Exemple

A B C D E F G Sous-arbre gauche H I P Q R S Sous-arbre droit L M N O Z AA DD EE

6. Arbre localement complet

Un arbre est dit localement complet si chaque noeud possède soit 0 soit deux enfants.

7. Exemple

Cet arbre est localement complet.

A B C D E F G H I L M N O P Q R S Z AA DD EE

8. Arbre complet

Un arbre est dit complet s'il est localement complet et que ses noeuds ne comportant pas d'enfant sont au même niveau.

9. Exemple

Cet arbre est complet.

A B C D E F G H I J K L M N O P Q R S T U V W X Y Z AA BB CC DD EE

10. Arbre dégénéré

Un arbre est dit dégénéré s'il ne possède qu'une seule feuille.

On peut alors l'associer à une structure linéaire.

11. Exemple

Cet arbre est dégénéré.

A B D H P

12. Exercice : représenter un arbre à partir d'un tableau.

À faire dans le cahier.

Représenter l'arbre correspondant au tableau ci-dessous :

Noeud Valeur Enfant gauche Enfant droit
1 René 2 3
2 Lilou 4 \(\emptyset\)
4 Léa \(\emptyset\) \(\emptyset\)
3 Julie \(\emptyset\) 6
6 Chloé 7 8
7 Titou \(\emptyset\) \(\emptyset\)
8 Irène \(\emptyset\) \(\emptyset\)

13. Exercice : tableau à partir d'un arbre.

À faire dans le cahier.

En prenant exemple sur le tableau de l'exercice précédent, faire un tableau correspondant à l'arbre ci-dessous :

Liam Emma Noah Olivia Lucas Ava Mia Ethan James Sophia

14. Une première implémentation dans Python

On peut stocker un arbre binaire dans Python à l'aide d'une liste de listes :

[racine,[sous_arbre_gauche],[sous_arbre_droit]]

15. Exercice : Une première implémentation dans Python

Insérer dans Python l'arbre suivant à l'aide d'une liste de listes.

Une feuille aura ce format : [valeur,None,None]

Ethan Sophia Mason Olivia James Mia
arbre = [..] print(f"La racine est {arbre[0]}.") print(f"Le sous arbre gauche est {arbre[1]}.") print(f"Le sous arbre droit est {arbre[2]}.") print(f"Le sous arbre gauche du sous arbre gauche est {arbre[1][1]}.")


    
>>>

16. Exercice : Ajout d’une feuille à gauche (récursif)

Écrire une fonction ajout_feuille_gauche(liste_arbre, valeur) qui prend en paramètre un arbre sous forme de liste et une valeur.

Elle fonctionnera de manière récursive ainsi :

  • Si l’enfant gauche de la racine est vide, on place la valeur à cet endroit.
  • Sinon, on exécute cette fonction dans le sous-arbre gauche.

Dans le contexte, un arbre est ici stocké dans des listes de listes (format [etiquette, gauche, droite]). Par exemple :

["Olivier",
 ["Julien", None, None],
 ["Léa", None, None]]
def ajout_feuille_gauche(liste_arbre, valeur): """ Modifie l'arbre en place : - si l'enfant gauche de la racine est vide, on insère [valeur, None, None] - sinon, on applique récursivement au sous-arbre gauche """ if ... is None: liste_arbre[1] = [valeur, None, None] else: ... arbre = ["Olivier", ["Julien", None, None], ["Léa", None, None]] print(f"Le sous arbre gauche est {arbre[1]}.") print("Ajout de la feuille Chloé...") ajout_feuille_gauche(arbre,"Chloé") print(f"Le sous arbre gauche est {arbre[1]}.") print(f"L' arbre est {arbre}.")

    
>>>

17. Algorithmes de la taille, de la hauteur et du nombre de feuilles d'un arbre.

Ces trois algorithmes sont des algorithmes récursifs. À droite de chaque algorithme, observe son exécution sur le même arbre : chaque nœud s'allume quand la fonction est appelée sur lui, et affiche la valeur renvoyée quand son appel se termine.

18. Exercice : Arbres — taille, hauteur, nombre de feuilles

On représente un arbre binaire par une liste de la forme [etiquette, gauche, droite], où gauche et droite sont soit None, soit des sous-arbres au même format.

Écrire en Python les fonctions suivantes :

  • taille(arbre) : renvoie le nombre total de nœuds de l’arbre.
  • hauteur(arbre) : renvoie la hauteur de l’arbre (un arbre réduit à un seul nœud a une hauteur égale à 1 ; l’arbre vide a une hauteur de 0).
  • nombre_de_feuilles(arbre) : renvoie le nombre de feuilles (nœuds sans enfant) de l’arbre.

Exemple de structure :

["Olivier",
 ["Julien", None, None],
 ["Léa", None, None]]
def taille(arbre): """Renvoie le nombre total de nœuds de l'arbre.""" if arbre is None: return ... gauche, droite = arbre[1], arbre[2] return ... def hauteur(arbre): """Renvoie la hauteur de l'arbre (1 pour un nœud seul, 0 si vide).""" if ...: return ... return 1 + max(..., ...) def nombre_de_feuilles(arbre): """Renvoie le nombre de feuilles (nœuds sans enfants).""" if arbre is None: return ... gauche, droite = arbre[1], arbre[2] if ... is None and ... is None: return ... return ... + ... # Jeux d'arbres pour les tests arbre_vide = None arbre_feuille = ['A', None, None] arbre_simple = ['Olivier', ['Julien', None, None], ['Léa', None, None]] arbre_medium = ['R', ['A', ['B', None, None], None], ['C', ['D', None, None], ['E', None, None]]] arbre_large = ['R', ['N2', ['N4', ['N8', ['N16', None, None], ['N17', None, None]], ['N9', ['N18', None, None], ['N19', None, None]]], ['N5', ['N10', ['N20', None, None], ['N21', None, None]], ['N11', ['N22', None, None], ['N23', None, None]]]], ['N3', ['N6', ['N12', ['N24', None, None], ['N25', None, None]], ['N13', ['N26', None, None], ['N27', None, None]]], ['N7', ['N14', ['N28', None, None], ['N29', None, None]], ['N15', ['N30', None, None], ['N31', None, None]]]]] arbre_a_tester = arbre_large print(f"Cet arbre a une taille de {taille(arbre_a_tester)}, une hauteur de {hauteur(arbre_a_tester)} et un nombre de feuilles égal à {nombre_de_feuilles(arbre_a_tester)}.")

    
>>>

19. Parcours en profondeur

Parcourir l'arbre en profondeur signifie que l'on explore chaque sous-arbre complètement avant de passer à un autre sous-arbre.

On distingue alors trois cas :

20. Exercice : parcours préfixe, infixe et postfixe.

À faire dans le cahier.

On considère l'arbre suivant :

A B C D E F G H I J K L M N O
  1. Écrire les valeurs rencontrées en parcourant l'arbre dans l'ordre préfixe.
  2. Écrire les valeurs rencontrées en parcourant l'arbre dans l'ordre infixe.
  3. Écrire les valeurs rencontrées en parcourant l'arbre dans l'ordre postfixe.

21. Algorithmes parcours en profondeur.

Ces trois algorithmes sont des algorithmes récursifs. C'est le même arbre qui est parcouru par les trois algorithmes : seul le moment où la racine est évaluée change, et donc l'ordre dans lequel les nœuds sont numérotés. Compare les trois séquences obtenues !

22. Exercice : Parcours d'arbre — préfixe, infixe, postfixe (afficher & renvoyer)

On représente un arbre binaire par une liste [etiquette, gauche, droite], où gauche et droite sont soit None, soit des sous-arbres au même format.

  1. lecture_prefixe(arbre) : renvoie la liste des valeurs rencontrées en ordre préfixe (racine, gauche, droite).
  2. lecture_infixe(arbre) : renvoie la liste des valeurs en ordre infixe (gauche, racine, droite).
  3. lecture_postfixe(arbre) : renvoie la liste des valeurs en ordre postfixe (gauche, droite, racine).
def lecture_prefixe(arbre): """Affiche les valeurs en ordre préfixe et renvoie la liste correspondante.""" if arbre is None: return [] etiquette, gauche, droite = arbre[0], arbre[1], arbre[2] liste_retournee = ... liste_retournee = liste_retournee + ... liste_retournee = liste_retournee + ... return liste_retournee def lecture_infixe(arbre): """Affiche les valeurs en ordre infixe et renvoie la liste correspondante.""" if ...: return ... etiquette, gauche, droite = arbre[0], arbre[1], arbre[2] liste_retournee = ... liste_retournee = liste_retournee + ... liste_retournee = liste_retournee + ... return liste_retournee def lecture_postfixe(arbre): """Affiche les valeurs en ordre postfixe et renvoie la liste correspondante.""" if ...: return ... etiquette, gauche, droite = arbre[0], arbre[1], arbre[2] liste_retournee = ... liste_retournee = liste_retournee + ... liste_retournee = liste_retournee + ... return liste_retournee # Arbres d'exemple # 'A' # / \ # 'B' 'C' # / \ \ # 'D' 'E' 'F' arbre_ex1 = ['A', ['B', ['D', None, None], ['E', None, None]], ['C', None, ['F', None, None]]] arbre_ex2 = ['Z', None, None] arbre_vide = None arbre_a_tester = arbre_ex1 print(f"Lecture prefixe de l'arbre : {lecture_prefixe(arbre_a_tester)}") print(f"Lecture infixe de l'arbre : {lecture_infixe(arbre_a_tester)}") print(f"Lecture postfixe de l'arbre : {lecture_postfixe(arbre_a_tester)}")

    
>>>

23. Implémentation à l'aide d'une classe

On peut implémenter un arbre dans Python avec une classe de différentes façons.

Afin de faciliter les différentes corrections, nous utiliserons la même qui est celle-ci :

class ArbreBinaire:
    def __init__(self, valeur):
        self.valeur = valeur
        self.gauche = None
        self.droit = None

    def ajout_gauche(self, valeur):
        self.gauche = ArbreBinaire(valeur)

    def ajout_droit(self, valeur):
        self.droit = ArbreBinaire(valeur)

    def suppr_gauche(self):
        self.gauche = None

    def suppr_droit(self):
        self.droit = None

24. Exercice : Construire l'arbre donné (classe fournie)

Liam Emma Noah Olivia Lucas Ava Mia Ethan James Sophia

Compléter le code pour créer l’instance arbre et tous ses descendants en utilisant ajout_gauche / ajout_droit.

class ArbreBinaire: def __init__(self, valeur): self.valeur = valeur self.gauche = None self.droit = None def ajout_gauche(self, valeur): self.gauche = ArbreBinaire(valeur) def ajout_droit(self, valeur): self.droit = ArbreBinaire(valeur) def suppr_gauche(self): self.gauche = None def suppr_droit(self): self.droit = None # ---------------------------- # À compléter : construction de l'arbre demandé # ---------------------------- arbre = ArbreBinaire("Liam") ...

    
>>>

25. Exercice : Taille, hauteur et nombre de feuilles (classe ArbreBinaire)

On représente un arbre binaire avec la classe ArbreBinaire (valeur, gauche, droit). Écrire les fonctions suivantes :

  • taille(arbre) : renvoie le nombre total de nœuds (0 si l’arbre est vide) ;
  • hauteur(arbre) : renvoie la hauteur (0 si vide, 1 si un seul nœud) ;
  • nombre_feuilles(arbre) : renvoie le nombre de feuilles (nœuds sans enfant).
class ArbreBinaire: def __init__(self, valeur): self.valeur = valeur self.gauche = None self.droit = None def ajout_gauche(self, valeur): self.gauche = ArbreBinaire(valeur) def ajout_droit(self, valeur): self.droit = ArbreBinaire(valeur) def taille(arbre): """Renvoie le nombre total de nœuds de l'arbre (0 si vide).""" if arbre is None: return ... return 1 + ... + ... def hauteur(arbre): """Renvoie la hauteur de l'arbre (0 si vide, 1 si un seul nœud).""" if ...: return ... return 1 + max(..., ...) def nombre_feuilles(arbre): """Renvoie le nombre de feuilles (nœuds sans enfant).""" if arbre is None: return ... if ... is None and ... is None: return ... return ... + ... # Arbres d'exemple pour les tests arbre_vide = None feuille = ArbreBinaire('Z') arbre_simple = ArbreBinaire('A') arbre_simple.ajout_gauche('B') arbre_simple.ajout_droit('C') arbre_medium = ArbreBinaire('R') arbre_medium.ajout_gauche('A') arbre_medium.gauche.ajout_gauche('B') arbre_medium.ajout_droit('C') arbre_medium.droit.ajout_gauche('D') arbre_medium.droit.ajout_droit('E') arbre_chaine = ArbreBinaire('X') arbre_chaine.ajout_gauche('Y') arbre_chaine.gauche.ajout_gauche('Z') arbre_a_tester = arbre_medium print(f"Cet arbre a une taille de {taille(arbre_a_tester)}, une hauteur de {hauteur(arbre_a_tester)} et un nombre de feuilles égal à {nombre_feuilles(arbre_a_tester)}.")

    
>>>

26. Exercice : Parcours d'arbre — préfixe, infixe, postfixe (classe ArbreBinaire)

On représente un arbre binaire avec la classe ArbreBinaire (valeur, gauche, droit). Écrire des fonctions qui renvoient la liste des valeurs rencontrées :

  1. lecture_prefixe(racine) : ordre préfixe (racine, gauche, droite) ;
  2. lecture_infixe(racine) : ordre infixe (gauche, racine, droite) ;
  3. lecture_postfixe(racine) : ordre postfixe (gauche, droite, racine).
class ArbreBinaire: def __init__(self, valeur): self.valeur = valeur self.gauche = None self.droit = None def ajout_gauche(self, valeur): self.gauche = ArbreBinaire(valeur) def ajout_droit(self, valeur): self.droit = ArbreBinaire(valeur) def lecture_prefixe(arbre): """Renvoie la liste des valeurs en ordre préfixe (racine, gauche, droite).""" if arbre is None: return [] liste_retournee = ... liste_retournee = liste_retournee + ... liste_retournee = liste_retournee + ... return liste_retournee def lecture_infixe(arbre): """Renvoie la liste des valeurs en ordre infixe (gauche, racine, droite).""" if ...: return ... liste_retournee = ... liste_retournee = liste_retournee + ... liste_retournee = liste_retournee + ... return liste_retournee def lecture_postfixe(arbre): """Renvoie la liste des valeurs en ordre postfixe (gauche, droite, racine).""" if ...: return ... liste_retournee = ... liste_retournee = liste_retournee + ... liste_retournee = liste_retournee + ... return liste_retournee # Arbres d'exemple # 'A' # / \ # 'B' 'C' # / \ \ # 'D' 'E' 'F' arbre_ex1 = ArbreBinaire('A') arbre_ex1.ajout_gauche('B'); arbre_ex1.ajout_droit('C') arbre_ex1.gauche.ajout_gauche('D'); arbre_ex1.gauche.ajout_droit('E') arbre_ex1.droit.ajout_droit('F') arbre_ex2 = ArbreBinaire('Z') arbre_vide = None arbre_a_tester = arbre_ex1 print(f"Lecture prefixe de l'arbre : {lecture_prefixe(arbre_a_tester)}") print(f"Lecture infixe de l'arbre : {lecture_infixe(arbre_a_tester)}") print(f"Lecture postfixe de l'arbre : {lecture_postfixe(arbre_a_tester)}")

    
>>>

27. Parcours de l'arbre en largeur (aussi appelé "en largeur d'abord")

Nous avons vu que nous pouvons parcourir un arbre en profondeur. Nous pouvons aussi le parcourir en largeur d'abord.

Il suffit d'évaluer les valeurs de chaque noeud niveau par niveau.

28. Exercice : parcours en largeur.

À faire dans le cahier.

On considère l'arbre suivant :

A B C D E F G H I J K L M N O

Dans votre cahier, écrire les valeurs des noeuds rencontrés par un parcours en largeur.

29. Algorithme du parcours en largeur d'un arbre :

Cet algorithme n'est pas récursif : c'est la file qui retient les sous-arbres qu'il reste à traiter. Observe l'animation sur le même arbre que plus haut : les nœuds entrent dans la file niveau par niveau, et sortent dans l'ordre où ils sont entrés (FIFO). Compare l'ordre obtenu avec ceux des parcours en profondeur !

 fonction parcours_largeur(arbre):
    si arbre vide:
        On ne fait rien
    sinon :
        on place l'arbre dans une file.
        tant que la file n'est pas vide:
            on sort un element de la file
            on affiche la valeur de la racine
            on place le sous-arbre gauche dans la file s'il n'est pas vide
            on place le sous-arbre droit dans la file s'il n'est pas vide
Entrée (enfiler) →Sortie (défiler) →
file vide
Ordre : —
nœud dans la file nœud qui sort de la file nœud évalué (numéroté)

Regarde la file se remplir niveau par niveau…

30. Exercice : Parcours en largeur (classe ArbreBinaire + File)

On représente un arbre binaire avec la classe ArbreBinaire (valeur, gauche, droit).

Écrire une fonction parcours_largeur(arbre) qui renvoie la liste des valeurs rencontrées lors du parcours en largeur de l’arbre (niveau par niveau de gauche à droite).

Pour cela, on utilisera une classe File permettant de gérer une file FIFO avec les méthodes :

  • enfiler(x) : ajoute un élément en fin de file ;
  • defiler() : retire et renvoie le premier élément de la file ;
  • est_vide() : renvoie True si la file est vide.

Exemple :

#         'A'
#        /   \ 
#      'B'   'C'
#     /  \      \
#   'D'  'E'    'F'
# Le parcours en largeur renvoie : ['A', 'B', 'C', 'D', 'E', 'F']
class ArbreBinaire: def __init__(self, valeur): self.valeur = valeur self.gauche = None self.droit = None def ajout_gauche(self, valeur): self.gauche = ArbreBinaire(valeur) def ajout_droit(self, valeur): self.droit = ArbreBinaire(valeur) class File: def __init__(self): self.__donnees=[] def enfiler(self, x): self.__donnees.insert(0, x) def defiler(self): return self.__donnees.pop() def est_vide(self): return self.__donnees == [] def parcours_largeur(arbre): """Renvoie la liste des valeurs en parcours en largeur (niveau par niveau).""" if arbre is None: return [] file = File() ... resultat = [] while not file.est_vide(): noeud = ... resultat.append(...) if noeud.gauche is not None: ... if noeud.droit is not None: ... return resultat # Arbre d'exemple arbre_ex1 = ArbreBinaire('A') arbre_ex1.ajout_gauche('B') arbre_ex1.ajout_droit('C') arbre_ex1.gauche.ajout_gauche('D') arbre_ex1.gauche.ajout_droit('E') arbre_ex1.droit.ajout_droit('F') print(f"Lecture en largeur de l'arbre : {parcours_largeur(arbre_ex1)}")

    
>>>

31. Calculs à l'aide d'un arbre

Un arbre permet de stocker un calcul tout en précisant les priorités opératoires à effectuer.

32. Exemple

On considère l'arbre suivant :

+ × / 3 2 + 60 4 6

Il permet de stocker le calcul suivant : \( 3 \times 2 + \frac{60}{4+6}\)

33. Exercice : parcours d'un arbre d'expression.

À faire dans le cahier.

On considère l'arbre de l'exemple précédent.

  1. Écrire les valeurs rencontrées en parcourant l'arbre dans l'ordre préfixe.
  2. Écrire les valeurs rencontrées en parcourant l'arbre dans l'ordre infixe.
  3. Écrire les valeurs rencontrées en parcourant l'arbre dans l'ordre postfixe.

34. Écriture en ligne d'un parcours postfixé ou préfixé d'un arbre contenant une expression mathématique :

Certains langages de programmation ou même de calculatrice utilisent de telles notations pour calculer.

35. Exemples

36. Exercice : Évaluer une expression dans un arbre binaire

On représente une expression mathématique sous forme d’arbre binaire à l’aide de la classe ArbreBinaire. Chaque nœud contient soit un opérateur (+, -, *, /), soit un entier.

Programmer une fonction Python evaluer qui prend comme paramètre un objet ArbreBinaire contenant une expression mathématique et qui renvoie le résultat numérique.

class ArbreBinaire: def __init__(self, valeur): self.valeur = valeur self.gauche = None self.droit = None def ajout_gauche(self, valeur): self.gauche = ArbreBinaire(valeur) def ajout_droit(self, valeur): self.droit = ArbreBinaire(valeur) def suppr_gauche(self): self.gauche = None def suppr_droit(self): self.droit = None def evaluer(arbre): """Évalue récursivement l'expression représentée par l'arbre binaire.""" if arbre.gauche is None and arbre.droit is None: return ... op = arbre.valeur g = ... d = ... if op == "+": return ... elif op == "-": return ... elif op == "*": return ... elif op == "/": return ... # Construction de l'arbre (3*2) + (60 / (4+6)) arbre = ArbreBinaire("+") arbre.ajout_gauche("*") arbre.gauche.ajout_gauche(3) arbre.gauche.ajout_droit(2) arbre.ajout_droit("/") arbre.droit.ajout_gauche(60) arbre.droit.ajout_droit("+") arbre.droit.droit.ajout_gauche(4) arbre.droit.droit.ajout_droit(6) print(f"Évaluation de l'arbre : {evaluer(arbre)}.")

    
>>>

37. Exercice : (sujet 35 - 2025) évaluation d'une expression postfixe avec une pile.

Nous avons l’habitude de noter les expressions arithmétiques avec des parenthèses comme par exemple : \((2 + 3) \times 5\).

Il existe une autre notation utilisée par certaines calculatrices, appelée notation postfixe, qui n’utilise pas de parenthèses. L’expression arithmétique précédente est alors obtenue en saisissant successivement 2, puis 3, puis l’opérateur +, puis 5, et enfin l’opérateur *. On modélise cette saisie par le tableau [2, 3, '+', 5, '*'].

Autre exemple, la notation postfixe de \(3 \times 2 + 5\) est modélisée par le tableau :

[3, 2, '*', 5, '+']

D’une manière plus générale, la valeur associée à une expression arithmétique en notation postfixe est déterminée à l’aide d’une pile en parcourant l’expression de gauche à droite :

  • si l’élément parcouru est un nombre, on le place au sommet de la pile,
  • si l’élément parcouru est un opérateur, on récupère les deux éléments situés au sommet de la pile et on leur applique l’opérateur, puis on place le résultat au sommet de la pile,
  • à la fin du parcours, il reste alors un seul élément dans la pile qui est le résultat de l’expression arithmétique.

Dans cet exercice, on se limitera aux opérations * et +.

Complète la fonction eval_expression qui reçoit en paramètre une liste python représentant la notation postfixe d’une expression arithmétique et qui renvoie sa valeur.

⭐ Exemple :

>>> eval_expression([2, 3, '+', 5, '*'])
25
class Pile: def __init__(self): self.__contenu = [] def est_vide(self): return self.__contenu == [] def empiler(self, v): self.__contenu.append(v) def depiler(self): assert not self.est_vide() return self.__contenu.pop() def eval_expression(tab): p = Pile() for ... in tab: if element != '+' ... element != '*': p.empiler(...) else: a = p.depiler() b = p.depiler() if element == ...: resultat = ... + ... else: resultat = ... p.empiler(resultat) return ... print(eval_expression([2, 3, '+', 5, '*']))


    
>>>

38. Exercice (sujet 30 - 2025) parcours infixe d'un arbre d'expression.

Une expression arithmétique ne comportant que les quatre opérations \(+\), \(-\), \( \times \), \( \div \) peut être représentée sous forme d’arbre binaire. Les nœuds internes sont des opérateurs et les feuilles sont des nombres. Dans un tel arbre, la disposition des nœuds joue le rôle des parenthèses que nous connaissons bien.

- * + 3 + 2 1 8 7

En parcourant en profondeur infixe l’arbre binaire ci-dessus, on retrouve l’expression notée habituellement :

\((3 \times (8 + 7)) – (2 + 1)\)

La classe Expr ci-après permet d’implémenter une structure d’arbre binaire pour représenter de telles expressions.

Compléter la méthode récursive infixe qui renvoie une chaîne de caractères contenant des parenthèses représentant l’expression arithmétique sur laquelle on l’applique.

class Expr: """Classe implémentant un arbre d'expression.""" def __init__(self, g, v, d): """un objet Expr possède 3 attributs : - gauche : la sous-expression gauche ; - valeur : la valeur de l’étiquette, opérateur ou nombre ; - droite : la sous-expression droite.""" self.gauche = g self.valeur = v self.droite = d def est_une_feuille(self): """renvoie True si et seulement si le noeud est une feuille""" return self.gauche is None and self.droite is None def infixe(self): """renvoie la représentation infixe de l'expression en chaîne de caractères""" s = ... if self.gauche is not None: s = s + '(' + self.gauche.infixe() s = s + ... if self.droite is not None: s = s + ... + ... return s # Exemples de test a = Expr(Expr(None, 1, None), '+', Expr(None, 2, None)) b = Expr(Expr(Expr(None, 1, None), '+', Expr(None, 2, None)), '*', Expr(Expr(None, 3, None), '+', Expr(None, 4, None))) e = Expr( Expr( Expr(None, 3, None), '*', Expr( Expr(None, 8, None), '+', Expr(None, 7, None)) ), '-', Expr( Expr(None, 2, None), '+', Expr(None, 1, None) ) ) expression_a_tester = a #peut être modifié print(f"Affichage de l'expression : {expression_a_tester.infixe()} ")

    
>>>

39. Exercice : (sujet 47 - 2025) taille d'un arbre stocké en dictionnaire.

Un arbre binaire de caractères est stocké sous la forme d’un dictionnaire où les clés sont les caractères des nœuds de l’arbre et les valeurs, pour chaque clé, la liste des caractères des fils gauche et droit du nœud.

Par exemple, l’arbre

F B G A D I C E H

est stocké dans

a = {'F':['B','G'], 'B':['A','D'], 'A':['',''], 'D':['C','E'],'C':['',''], 'E':['',''], 'G':['','I'], 'I':['','H'], 'H':['','']}

Écrire une fonction récursive taille prenant en paramètres un arbre binaire arbre sous la forme d’un dictionnaire et un caractère lettre qui est la valeur du sommet de l’arbre, et qui renvoie la taille de l’arbre, à savoir le nombre total de nœud.

On observe que, par exemple, arbre[lettre][0], respectivement arbre[lettre][1], permet d’atteindre la clé du sous-arbre gauche, respectivement droit, de l’arbre arbre de sommet lettre.

Exemple :

>>> taille(a, 'F')
9
# Dictionnaire de l'exemple officiel a = {'F':['B','G'], 'B':['A','D'], 'A':['',''], 'D':['C','E'],'C':['',''], 'E':['',''], 'G':['','I'], 'I':['','H'], 'H':['','']} def taille(arbre, lettre): ... racine = "F" print(f"Taille de l'arbre de racine {racine} : {taille(a, racine)} .")

    
>>>

40. Exercice : parcours préfixe itératif avec une pile.

Il est moins usuel mais nous pouvons programmer un parcours préfixé en itératif à l'aide de l'algorithme suivant :

fonction parcours_prefixe_iteratif(arbre):
    créer une pile p
    insérer l'arbre dans la pile 
    tant que la pile p n'est pas vide 
        depiler un element de la pile que l'on nommera element 
        traiter la racine de cet element en l'affichant par exemple
        si le sous-arbre droit n'est pas vide :
            on empile dans p le sous-arbre droit
        si le sous arbre gauche n'est pas vide :
            on empile dans p le sous-arbre gauche

Coder ceci en python à l'aide des classes ArbreBinaire et Pile. On pourra utiliser un arbre des exercices précédents.

class ArbreBinaire: def __init__(self, valeur): self.valeur = valeur self.gauche = None self.droit = None def ajout_gauche(self, valeur): self.gauche = ArbreBinaire(valeur) def ajout_droit(self, valeur): self.droit = ArbreBinaire(valeur) class Pile: def __init__(self): self._d = [] def empiler(self, x): self._d.append(x) def depiler(self): return self._d.pop() def est_vide(self): return self._d == [] def parcours_prefixe_iteratif(arbre): """ Renvoie la liste des valeurs de l'arbre en parcours préfixe (racine, gauche, droite) en utilisant une pile (itératif). """ if arbre is None: return [] p = Pile() p.empiler(arbre) resultat = [] while not p.est_vide(): element = ... resultat.append(...) if element.droit is not None: ... if element.gauche is not None: ... return resultat # Arbre d'exemple (même structure que précédemment) # 'A' # / \ # 'B' 'C' # / \ \ # 'D' 'E' 'F' arbre_ex1 = ArbreBinaire('A') arbre_ex1.ajout_gauche('B'); arbre_ex1.ajout_droit('C') arbre_ex1.gauche.ajout_gauche('D'); arbre_ex1.gauche.ajout_droit('E') arbre_ex1.droit.ajout_droit('F') arbre_vide = None arbre_ex2 = ArbreBinaire('Z') arbre_ex3 = ArbreBinaire('R') arbre_ex3.ajout_gauche('L'); arbre_ex3.ajout_droit('S') arbre_ex3.droit.ajout_gauche('T') arbre_a_tester = arbre_ex1 print(f"Valeurs rencontrées lors du parcours préfixe : {parcours_prefixe_iteratif(arbre_a_tester)} .")

    
>>>

41. Exercice : (sujet 11 - 2025) parcours en largeur d'un arbre binaire.

Un arbre binaire est soit vide, représenté en Python par la valeur None, soit un nœud représenté par un triplet (g, x, d)x est l’étiquette du nœud et g et d sont les sous-arbres gauche et droit.

On souhaite écrire une fonction parcours_largeur qui prend en paramètre un arbre binaire et qui renvoie la liste des étiquettes des nœuds de l’arbre parcourus en largeur.

Exemples :

>>> arbre = ( ( (None, 1, None), 2, (None, 3, None) ),
...           4,
...           ( (None, 5, None), 6, (None, 7, None) ) )
>>> parcours_largeur(arbre)
[4, 2, 6, 1, 3, 5, 7]
# Exemple officiel arbre = ( ( (None, 1, None), 2, (None, 3, None) ), 4, ( (None, 5, None), 6, (None, 7, None) ) ) def parcours_largeur(a): """Renvoie la liste des étiquettes par parcours en largeur d'un arbre binaire représenté par des triplets (g, x, d) ou None pour l'arbre vide.""" # Autres arbres de test possibles arbre_vide = None arbre_seul = (None, 42, None) arbre_gauche = ((None, 2, None), 1, None) arbre_droit = (None, 1, (None, 3, None)) arbre_a_tester = arbre print(f"Parcours en largeur de l'arbre : {parcours_largeur(arbre_a_tester)} .")

    
>>>