Pour exemple, prenons un arbre.
Supposons que nous voulions effectuer un traitement sur chaque nœud de l'arbre.
Par exemple afficher le contenu, ou chercher une valeur, …
traitement(noeud),On doit donc parcourir tous les nœuds, une fois, sans en oublier…
Mais dans quel ordre ?
Les parties ci-dessous correspondent à différents choix possibles.
On effectue le traitement, niveau après niveau, dans l'ordre de la lecture : A, B, C, D, E, F, G, J, H
On utilise une file.
FONCTION parcourir_arbre_en_largeur
ENTRÉE : arbre
DÉBUT
créer une file vide,
enfiler la racine de l'arbre
TANT QUE la file n'est pas vide RÉPÉTER
défiler un noeud
enfiler les enfants de ce noeud
exécuter le traitement sur ce noeud
FIN
FIN
Ce n'est pas si simple, détaillons un peu l'algorithme.
On notera le contenu de la file sous la forme <A:B:C:D< où les éléments entrent par la droite et sortent par la gauche, dans le sens des flèches.
<<<A<<<<B:C<<C<<C:D:E<<D:E<<D:E:F:G<<E:F:G<La file fonctionne sur le principe du premier arrivé, premier servi. Comme on rencontre les parents en premiers, ils sont les premiers servis.
C'est un peu plus compliqué :
C'est le plus simple et le plus naturel.
C'est l'ordre – pour l'exemple – A, B, D,E, J, H, C, F, G
Vous constatez que l'on traite tout le sous arbre de B avant de traiter C.
On retrouve le même algorithme, mais avec une pile.
FONCTION parcourir_arbre_en_profondeur
ENTRÉE: arbre
DÉBUT
créer une pile vide,
empiler la racine de l'arbre
TANT QUE la pile n'est pas vide RÉPÉTER
dépiler un noeud
empiler les enfants de ce noeud dans l'ordre inverse
exécuter le traitement sur ce noeud
FIN
FIN
Ce n'est pas si simple, détaillons un peu l'algorithme.
On notera le contenu de la pile sous la forme A:B:C:D: où, dans ce cas, D est le dessus de la pile.
:A::C:B:C:C:E:D:C:E:C:C:H:J:Vous remarquez que le principe de la pile – dernier arrivé, premier servi – conduit à traiter les enfants de B avant le “frère” de B.
Vous remarquez aussi qu'on empile les enfants à l'envers pour qu'ils soient à l'endroit au moment du dépilement.
C'est un peu plus compliqué car on doit dissocier le moment où un nœud est parcouru et le moment où il est traité.
Prenons l'exemple de B : On devra parcourir B avant de partir à la découverte de sa descendance D et E, mais on doit attendre d'avoir terminer le parcours de toute la descendance avant de pouvoir traiter B…
Il serait possible de trouver une méthode de même genre que la précédente, en utilisant deux piles par exemple, une pile pour le parcours et une pile pour le traitement. Ce serait bien-sûr un peu plus compliqué.
À la place, je vous propose une approche différente : une approche récursive.
La récursivité permet une écriture très simple et intuitive mais cache des difficulté techniques que nous aborderons plus tard.
Nous définissions la fonction suivante :
FONCTION : parcourir_noeud
ENTRÉE : le noeud à traiter
DÉBUT
POUR CHAQUE enfant du noeud RÉPÉTER
exécuter parcourir_noeud avec l'enfant
FIN
traiter le noeud
FIN
parcourir arrête de s'appeler elle-même. Sans cela, l'algorithme n'aurait pas de fin.Le programme principal est maintenant très simple :
FONCTION : parcourir_arbre_en_profondeur_suffixe
ENTRÉE : arbre
DÉBUT
SI arbre n'est pas vide ALORS
exécuter parcourir_noeud avec la racine
FIN
FIN
En utilisant le fichier parcours_arbre.py définissant une ébauche de module pour arbre binaire, implémentez les parcours en largeur et parcours en profondeur.