Chap. 5 : Arbres - Cours (partie 3)
1. Parcours d’un arbre
Parcourir un arbre signifie visiter tous ses nœuds. Il existe deux grandes catégories de parcours.
1.1. Parcours en Profondeur (DFS)
Dans un parcours en profondeur, on explore complètement une branche avant de passer à la suivante. Comme l’arbre est une structure récursive, ces parcours s’écrivent très naturellement de manière récursive.
Il existe trois ordres principaux selon le moment où l’on traite la racine par rapport à ses fils :
- Préfixe : Racine, puis Gauche, puis Droit.
- Infixe : Gauche, puis Racine, puis Droit. (Pour un ABR, cela donne les valeurs triées !)
- Suffixe (ou Postfixe) : Gauche, puis Droit, puis Racine.
Implémentation Python
def parcours_prefixe(arbre):
if arbre is not None:
print(arbre.etiquette) # Traitement de la racine
parcours_prefixe(arbre.gauche)
parcours_prefixe(arbre.droit)
def parcours_infixe(arbre):
if arbre is not None:
parcours_infixe(arbre.gauche)
print(arbre.etiquette) # Traitement de la racine
parcours_infixe(arbre.droit)
def parcours_suffixe(arbre):
if arbre is not None:
parcours_suffixe(arbre.gauche)
parcours_suffixe(arbre.droit)
print(arbre.etiquette) # Traitement de la racine1.2. Parcours en Largeur (BFS)
Le parcours en largeur visite les nœuds niveau par niveau : d’abord la racine (niveau 1), puis ses enfants (niveau 2), puis les enfants de ses enfants (niveau 3), etc.
Pour implémenter ce parcours, la récursivité n’est pas adaptée. On utilise une structure de File (First In, First Out).
Algorithme
- Enfiler la racine.
- Tant que la file n’est pas vide :
- Défiler un nœud \(N\).
- Traiter \(N\) (ex: l’afficher).
- Enfiler le fils gauche de \(N\) (s’il existe).
- Enfiler le fils droit de \(N\) (s’il existe).
Implémentation Python optimisée
Utiliser une liste Python (list) comme une file avec pop(0) est inefficace (complexité \(\mathcal{O}(n)\) pour le décalage des éléments). Il faut utiliser collections.deque qui permet des opérations en \(\mathcal{O}(1)\) aux deux extrémités.
from collections import deque
def parcours_largeur(arbre):
if arbre is None:
return
file = deque([arbre])
while file: # Tant que la file n'est pas vide
noeud = file.popleft() # Défiler (O(1))
print(noeud.etiquette)
if noeud.gauche is not None:
file.append(noeud.gauche)
if noeud.droit is not None:
file.append(noeud.droit)2. Algorithmes sur les ABR
2.1. Recherche d’une clé
La recherche dans un ABR exploite la propriété d’ordre : à chaque nœud, on sait si on doit aller à gauche (valeur cherchée plus petite) ou à droite (valeur cherchée plus grande).
def recherche_abr(arbre, valeur) -> bool:
if arbre is None:
return False
if valeur == arbre.etiquette:
return True
elif valeur < arbre.etiquette:
return recherche_abr(arbre.gauche, valeur)
else:
return recherche_abr(arbre.droit, valeur)2.2. Complexité de la recherche
La complexité dépend de la forme de l’arbre, donc de sa hauteur \(h\). À chaque étape, on descend d’un niveau. Le nombre d’étapes maximal est donc \(h\).
- Pire cas (Arbre filiforme) : \(h = n\). La complexité est linéaire \(\mathcal{O}(n)\). C’est le cas si on insère des valeurs déjà triées (1, 2, 3, 4…).
- Meilleur cas (Arbre parfait/équilibré) : \(h \approx \log_2 n\). La complexité est logarithmique \(\mathcal{O}(\log n)\).
C’est pour cela qu’il est important d’avoir des arbres équilibrés.
3. Autres exemples
Exercice 1 : Somme des valeurs
Écrire une fonction récursive qui renvoie la somme de toutes les valeurs d’un arbre d’entiers.
Solution
def somme(arbre):
if arbre is None:
return 0
return arbre.etiquette + somme(arbre.gauche) + somme(arbre.droit)Exercice 2 : Maximum d’un ABR
Écrire une fonction efficace pour trouver la valeur maximale dans un ABR. (Indice : où se trouve le plus grand élément ?)
Solution
def maximum_abr(arbre):
if arbre is None:
return None
# On va le plus à droite possible
courant = arbre
while courant.droit is not None:
courant = courant.droit
return courant.etiquette




