====== Parcours d'un arbre ====== Pour exemple, prenons un arbre. {{ :nsi:terminales:arbres:arbre_8.png?nolink&400 |}} Supposons que nous voulions effectuer un traitement sur chaque nœud de l'arbre. Par exemple afficher le contenu, ou chercher une valeur, ... * on dispose d'une fonction ''%%traitement(noeud)%%'', * on souhaite l'exécuter tour à tour sur chaque nœud de l'arbre 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. ===== Parcours en largeur ===== On effectue le traitement, niveau après niveau, dans l'ordre de la lecture : A, B, C, D, E, F, G, J, H ==== Méthode ==== 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 ==== Suivi de l'algorithme ==== Ce n'est pas si simple, détaillons un peu l'algorithme. {{ :nsi:terminales:arbres:arbres_parcours_largeur.pdf |Version diaporama}} On notera le contenu de la file sous la forme '' ''%%<<%%'' * Ligne 5, la file reçoit A -> '' '' nœud en cours B et file '' '' nœud en cours C et file '' '' nœud en cours D et file '' 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 === Suivi de l'algorithme === Ce n'est pas si simple, détaillons un peu l'algorithme. {{ :nsi:terminales:arbres:arbres_parcours_profondeur_prefixe.pdf |Version diaporama}} 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. * Ligne 4, la pile est vide -> '':'' * Ligne 5, la pile reçoit A -> ''A:'' * Ligne 6, la pile n'étant pas vide, on exécute la boucle * Ligne 7, on dépile, le nœud en cours est donc A et la pile contient '':'' * Ligne 8, on empile les enfants de A à l'envers -> ''C:B:'' * Ligne 9, on exécute le traitement sur A * Ligne 6, la pile n'est pas vide * Ligne 7, dépilement -> nœud en cours B et pile ''C:'' * Ligne 8, empilement des enfants de B à l'envers -> ''C:E:D:'' * Ligne 9, traitement de B * Ligne 6, pile non vide * Ligne 7, dépilement -> nœud en cours D et pile ''C:E:'' * Ligne 8, empilement des enfants de D... il n'y en a pas * Ligne 9, traitement de D * Ligne 6, pile non vide * Ligne 7, dépilement -> nœud en cours E et pile ''C:'' * Ligne 8, empilement des enfants de E à l'envers -> ''C:H:J:'' * Ligne 9, traitement de E * etc. 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. ==== Parcours suffixe ==== 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**. === Méthode récursive === La [[nsi:terminales:recursivite:cours|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 * Cette fonction est récursive puisque elle s'appelle elle-même en ligne 5. * Quand on en arrive aux feuilles de l'arbre, le nœud en cours n'a pas d'enfant et la fonction ''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 ===== À faire ===== En utilisant le fichier {{ :nsi:terminales:arbres:parcours_arbre.py |}} définissant une ébauche de module pour arbre binaire, implémentez les parcours en largeur et parcours en profondeur.