Les arbres binaires sont des structures de données hiérarchiques (ses nœuds sont liés par des relations père-fils) et récursives.
On peut implémenter un arbre binaire et ses nœuds en Python par les classes Arbre et Noeud comme celles-ci :
class Arbre:
def __init__(self, racine = None):
self.racine = racine # type : Noeud
class Noeud:
def __init__(self, v, g = None, d = None):
self.g = g # type Noeud
self.d = d # type Noeud
self.v = v
La structure d’un Noeud est intrinsèquement récursive : parmi les attributs d’un Noeud, il y a des objets de type Noeud !
Calcul de la hauteur d’un arbre
Pour calculer la hauteur d’un arbre, il faut parcourir toutes ses branches et retenir la profondeur de la feuille la plus éloignée.
La fonction est bien entendue récursive : la hauteur d’un arbre est égale à la plus grande des hauteurs de ses deux branches …
fonction hauteur(arbre)
si la racine de l'arbre est une feuille
renvoyer 0
sinon
renvoyer 1 + maximum(hauteur(branche droite de la racine), hauteur(branche gauche de la racine))
fin si
fin fonction
Remarque : la valeur renvoyée par le cas de base est ici 0 car on a défini la profondeur de la racine d’un arbre à 0. Cette définition peut varier !
Quelques explications ...
La fonction attend un argument de type
Noeud car chaque
nœud est vu comme la
racine d’un
sous-arbre (une
branche).

Lorsque le nœud n’existe pas (fils d’une feuille) la fonction renvoie 0 (car il faut compter à partir de zéro, profondeur du nœud racine)
Implémenter cet algorithme en Python, et tester avec l’arbre défini par :
ng, nd = Noeud(9), Noeud(4)
ng, nd = Noeud(1, ng, nd), Noeud(12)
nd = Noeud(6, ng, nd)
ngd = Noeud(3)
ng = Noeud(10, d = ngd)
arbre = Arbre(Noeud(5, ng, nd))

CORRECTION
Avec une fonction (l’argument peut être None = « arbre vide ») :
def hauteur(noeud):
if noeud is None: # le noeud n'existe pas ...
return -1 # on retire le 1 déja compté !
else:
return 1 + max(hauteur(noeud.g), hauteur(noeud.d))
ou bien, si on n’accepte pas l’argument None :
def hauteur(noeud):
g = d = 0
if noeud.g is not None:
g = hauteur(noeud.g)
if noeud.d is not None:
d = hauteur(noeud.d)
return 1+ max(g, d)
Avec une méthode de Noeud (l’argument self ne peut pas être None !) :
def hauteur(self):
""" Renvoie la hauteur de la branche de racine self
(méthode récursive)
"""
if self.est_feuille():
return 0
elif self.g is None:
return self.d.hauteur()
elif self.d is None:
return self.g.hauteur()
else:
return 1 + max(self.g.hauteur(), self.d.hauteur())
Calculer l’ordre de complexité de cet algorithme.
Calcul de la taille d’un arbre
Pour calculer la taille d’un arbre, il faut là encore parcourir toutes ses branches et compter les nœuds. La fonction est également récursive.
Implémenter en Python un algorithme permettant de déterminer la taille d’un arbre, et tester avec l’arbre défini précédemment.
CORRECTION
Avec une fonction (l’argument peut être None = « arbre vide ») :
def taille(noeud):
if noeud is None:
return 0
else:
return 1 + taille(noeud.g) + taille(noeud.d)
Avec une méthode de Noeud (l’argument self ne peut pas être None !) :
def taille(self):
""" Renvoie la taille de la branche de racine self
(méthode récursive)
"""
c = 1
if self.d is not None:
c += self.d.taille()
if self.g is not None:
c += self.g.taille()
return c
Calcul du nombre de feuilles d’un arbre
Pour calculer le nombre de feuilles d’un arbre, il faut là encore parcourir toutes ses branches et compter les nœuds. La fonction est également récursive.
Implémenter en Python un algorithme permettant de compter le nombre de feuilles d’un arbre, et tester avec l’arbre défini précédemment.
Test de l’égalité de deux arbres
Pour tester si un arbre est égal à un autre, on souhaite utiliser la méthode spéciale __eq__.
Implémenter une méthode __eq__ pour la classe Arbre, et une autre pour la classe Noeud (récursive celle-ci).
Parcourir un arbre
Il existe plusieurs façons de parcourir un arbre …
Ordres infixe, préfixe ou suffixe
Ces trois modes se distinguent uniquement par l’ordre avec lequel on réalise les parcours des branches droite et gauche par rapport à la visite d’un nœud :
Ordre infixe
- Parcours branche gauche
- Visite du nœud
- Parcours branche droite

visiterInfixe(noeud)
si nonVide(noeud.gauche)
visiterInfixe(noeud.gauche)
visiter(noeud)
si nonVide(noeud.droit)
visiterInfixe(noeud.droit)
Activité
Sur feuille, lister les nœuds de l’arbre suivant selon l’ordre du parcours infixe.

(chiffres séparés par un espace chacun)
Ordre préfixe
- Visite du nœud
- Parcours branche gauche
- Parcours branche droite

visiterPréfixe(noeud)
visiter(noeud)
si nonVide(noeud.gauche)
visiterPréfixe(noeud.gauche)
si nonVide(noeud.droit)
visiterPréfixe(noeud.droit)
Activité
Sur feuille, lister les nœuds de l’arbre suivant selon l’ordre du parcours préfixe.

(chiffres séparés par un espace chacun)
Ordre suffixe
- Parcours branche gauche
- Parcours branche droite
- Visite du nœud

visiterSuffixe(noeud)
si nonVide(noeud.gauche)
visiterSuffixe(noeud.gauche)
si nonVide(noeud.droit)
visiterSuffixe(noeud.droit)
visiter(noeud)
Activité
Sur feuille, lister les nœuds de l’arbre suivant selon l’ordre du parcours suffixe.

(chiffres séparés par un espace chacun)
Implémenter en Python ces 3 méthodes de parcours, et tester avec l’arbre défini précédemment.
Dans un premier temps, « visiter » un nœud reviendra à l’afficher.
Dans un deuxième temps, faire en sorte que ces fonctions retournent une liste comportant l’ensemble des valeurs des nœuds, dans l’ordre de la visite.
Activités complémentaires
Utiliser une des méthodes précédentes pour rechercher la plus grande valeurs des nœuds d'un arbre binaire.
Utiliser une des méthodes précédentes pour obtenir le nombre d'occurrences d'un élément dans un arbre binaire.
Parcours en largeur d’abord
Le principe de ce parcours est de visiter le nœud le plus proche de la racine qui n’a pas déjà été visité, ainsi on va d’abord visiter la racine, puis les nœuds à la profondeur 1, puis 2, etc… D’où le nom parcours en largeur.

L’implémentation va nécessiter l’utilisation une structure en file : on met « en attente » les pères, puis on visite.
f = File
f.enfiler(noeud) //on place la racine dans la file
tant que f non vide :
n = f.defiler()
visiter(n)
si n.gauche n'est pas vide
f.enfiler(n.gauche)
fin si
si n.droit n'est pas vide
f.enfiler(n.droit)
fin si
fin tant que
Affichage
Doter la classe Arbre d’une méthode __repr__ permettant l’affichage d’un arbre sous forme de chaînes de caractères.
Exemple : l’arbre précédent doit s’afficher ainsi :
___5______
/ \
10 __6
\ / \
3 1 12
/ \
9 4
CORRECTION
class Arbre:
def __init__(self, racine = None):
self.racine = racine # type : Noeud
def __repr__(self):
""" Affiche les valeurs des noeuds sous forme arborescente
"""
lines = _build_tree_string(self.racine, 0, False, '-')[0]
return '\n' + '\n'.join((line.rstrip() for line in lines))
# Source : https://github.com/joowani/binarytree/blob/22fc4b72c3be23d08016a2c863e6268f8ad49068/binarytree/__init__.py#L160
def _build_tree_string(root, curr_index, index=False, delimiter='-'):
"""Recursively walk down the binary tree and build a pretty-print string.
In each recursive call, a "box" of characters visually representing the
current (sub)tree is constructed line by line. Each line is padded with
whitespaces to ensure all lines in the box have the same length. Then the
box, its width, and start-end positions of its root node value repr string
(required for drawing branches) are sent up to the parent call. The parent
call then combines its left and right sub-boxes to build a larger box etc.
:param root: Root node of the binary tree.
:type root: binarytree.Node
:param curr_index: Level-order_ index of the current node (root node is 0).
:type curr_index: int
:param index: If set to True, include the level-order_ node indexes using
the following format: ``{index}{delimiter}{value}`` (default: False).
:type index: bool
:param delimiter: Delimiter character between the node index and the node
value (default: '-').
:type delimiter:
:return: Box of characters visually representing the current subtree, width
of the box, and start-end positions of the repr string of the new root
node value.
:rtype: ([str], int, int, int)
.. _Level-order:
https://en.wikipedia.org/wiki/Tree_traversal#Breadth-first_search
"""
if root is None:
return [], 0, 0, 0
line1 = []
line2 = []
if index:
node_repr = '{}{}{}'.format(curr_index, delimiter, root.v)
else:
node_repr = str(root.v)
new_root_width = gap_size = len(node_repr)
# Get the left and right sub-boxes, their widths, and root repr positions
l_box, l_box_width, l_root_start, l_root_end = \
_build_tree_string(root.g, 2 * curr_index + 1, index, delimiter)
r_box, r_box_width, r_root_start, r_root_end = \
_build_tree_string(root.d, 2 * curr_index + 2, index, delimiter)
# Draw the branch connecting the current root node to the left sub-box
# Pad the line with whitespaces where necessary
if l_box_width > 0:
l_root = (l_root_start + l_root_end) // 2 + 1
line1.append(' ' * (l_root + 1))
line1.append('_' * (l_box_width - l_root))
line2.append(' ' * l_root + '/')
line2.append(' ' * (l_box_width - l_root))
new_root_start = l_box_width + 1
gap_size += 1
else:
new_root_start = 0
# Draw the representation of the current root node
line1.append(node_repr)
line2.append(' ' * new_root_width)
# Draw the branch connecting the current root node to the right sub-box
# Pad the line with whitespaces where necessary
if r_box_width > 0:
r_root = (r_root_start + r_root_end) // 2
line1.append('_' * r_root)
line1.append(' ' * (r_box_width - r_root + 1))
line2.append(' ' * r_root + '\\')
line2.append(' ' * (r_box_width - r_root))
gap_size += 1
new_root_end = new_root_start + new_root_width - 1
# Combine the left and right sub-boxes with the branches drawn above
gap = ' ' * gap_size
new_box = [''.join(line1), ''.join(line2)]
for i in range(max(len(l_box), len(r_box))):
l_line = l_box[i] if i < len(l_box) else ' ' * l_box_width
r_line = r_box[i] if i < len(r_box) else ' ' * r_box_width
new_box.append(l_line + gap + r_line)
# Return the new box, its width and its root repr positions
return new_box, len(new_box[0]), new_root_start, new_root_end
Bonjour,
Les élèves ont remarqué quelques erreurs dans la correction, donc c’est toujours validé à faux
cordialement,
Bonjour
Si vous évoquez les zones de saisie avec bouton « Vérifier », je ne vois pas d’erreur. Pouvez-vous préciser SVP ?