Arbres - Parcours
I. Retour sur les structures de donnĂ©es abstraites.âïž
Quelles techniques avons-nous pour accéder aux éléments ?
1. Pour le type abstrait liste :
Solution
Renvoyer la tĂȘte de la liste
2. Pour le type abstrait file :
Solution
DĂ©filer
3. Pour le type abstrait pile :
Solution
DĂ©piler
4. Pour le type abstrait dictionnaire :
Solution
AccÚs par clé
. Pour le type list de python :
Solution
AccÚs par index, ou par élément
II. Rappel : Les Arbre binairesâïž
Arbres binaires
- Un arbre binaire est une structure permettant de stocker une collection de donnĂ©es de mĂȘme type.
- Ce n'est pas une structure linéaire.
- Le principal avantage des arbres par rapport aux listes est quâils permettent de ranger les donnĂ©es de telle sorte que les recherches soient plus efficaces.
- Pour accĂ©der Ă un Ă©lĂ©ment quelconque dâun arbre , il faut "descendre" dans lâarbre jusquâĂ cet Ă©lĂ©ment.
Arbre généalogique de Louis XIV
Comme nous l'avons déjà vu, certaines données ont naturellement une structure d'arbre binaire. C'est le cas d'un arbre généalogique ascendant (recherche du pÚre et de la mÚre) .
Exemple pour Louis XIV :
graph TD
D(Henri IV)
E(Maria de Medicis)
F(Felipe III d'Espagne)
G(Margareta d'Autriche)
B(Louis XIII)
C(Anna d'Autriche)
A(Louis XIV)
D --- B
E --- B
F --- C
G --- C
B --- A
C --- A
đ De façon plus conforme Ă la thĂ©orie des arbres, nous aurions pu reprĂ©senter cet arbre de la façon suivante, en plaçant la racine en haut :
graph TD
A(Louis XIV)
B(Louis XIII)
C(Anna d'Autriche)
D(Henri IV)
E(Maria de Medicis)
F(Felipe III d'Espagne)
G(Margareta d'Autriche)
A --- B
A --- C
B --- D
B --- E
C --- F
C --- G
Les parcours
-
Un parcours en largeur nous permettra de parcourir successivement toutes les personnes d'une mĂȘme gĂ©nĂ©ration.
-
Un parcours en profondeur nous permettra de parcourir l'arbre "par branche".
III. đâ Parcours en largeur des arbresâïž
BFS
- Ce parcours est parfois noté BFS pour Breadth-First Search
- Le parcours en largeur correspond Ă un parcours par niveau de noeuds de l'arbre. Un niveau est un ensemble de nĆuds ou de feuilles situĂ©s Ă la mĂȘme profondeur.
đ C'est un parcours Ă©tage par Ă©tage (de haut en bas) et de gauche Ă droite.
Dans cet exemple, on obtient successivement : 3, 1, 4, 5, 2, 0.
đ Pour Ă©tudier cet algorithme de parcours en largeur, nous allons utiliser une file.
Les files
Quel est le principe d'une file ?
Solution
En informatique, une file (queue en anglais ) est une structure de donnĂ©es basĂ©e sur le principe «Premier entrĂ©, premier sorti», en anglais FIFO (First In, First Out), ce qui veut dire que les premiers Ă©lĂ©ments ajoutĂ©s Ă la file seront les premiers Ă ĂȘtre rĂ©cupĂ©rĂ©s.
Le module queue
de Python
Nous avons déjà vu plusieurs implémentations possibles des files, nous allons ici utiliser celle de Python : le module Queue
Extrait de la documentation en français de python sur le module Queue qui est une implémentation des files. (Documentation officielle)
Les objets Queue
(Queue, LifoQueue ou PriorityQueue) fournissent les méthodes publiques décrites ci-dessous.
f = Queue()
Créé une file vide nomméef
.f.qsize()
renvoie la taille de la filef
.f.empty()
renvoieTrue
si la filef
est vide,False
sinonf.put(item)
ajouteitem
dans la filef
f.get()
défile (retire) et renvoie l'élément défilé de la filef
.
D'aprĂšs le cours de Gilles Lassus
MĂ©thode
- On place l'arbre dans la file.
- Tant que la file n'est pas vide, on procĂšde comme suit :
- On défile, donc on récupÚre l'arbre situé en haut de la file.
- Si cet arbre n'est pas vide :
- On garde son Ă©tiquette.
- On enfile son sous-arbre gauche, puis son sous-arbre droit.
Ă vous
Ecrire les Ă©tats suivants de la file. Nous admettons ici qu'un arbre vide est None
.
Solution
Classe Arbre
simplifiée, sans encapsulation
Exécuter le script suivant :
Créer l'arbre tests
Compléter le script suivant pour créer l'arbre de la figure ci-dessus
# Tests
(insensible Ă la casse)(Ctrl+I)
(Ctrl+Clic pour inverser les colonnes)
Solution
# arbre-test
a = Arbre(8)
a.left = Arbre(4)
a.right = Arbre(5)
a.left.left = Arbre(2)
a.left.right = Arbre(1)
a.right.right = Arbre(3)
Ă vous
Compléter le script suivant : la fonction parcours_BFS
doit renvoyer la liste des noeuds obtenue par le parcours en largeur de l'arbre.
# Tests
(insensible Ă la casse)(Ctrl+I)
(Ctrl+Clic pour inverser les colonnes)
Solution
def parcours_BFS(arbre):
file = Queue()
file.put(arbre)
solution = []
while not file.empty():
a = file.get()
if a is not None :
solution.append(a.data)
file.put(a.left)
file.put(a.right)
return solution
Tester
Tester ci-dessous la fonction parcours_BFS
pour l'arbre test créé au-dessus.
# Tests
(insensible Ă la casse)(Ctrl+I)
(Ctrl+Clic pour inverser les colonnes)
Solution
print(parcours_BFS(a))
â A noter ... et Ă mĂ©moriser ... đ
f = File() # Création d'une file vide f.enfiler(arbre)
tant que f non vide : arbre_au_sommet = f.defiler() si arbre_au_sommet n'est pas vide On garde son Ă©tiquette f.enfiler(son sous-arbre gauche) f.enfiler(son sous-arbre droit) fin si fin tant que
IV. Les parcours en profondeur - GĂ©nĂ©ralitĂ©sâïž
Le principe
-
Dans le cas d'un parcours en profondeur, l'un des deux sous-arbres est complÚtement exploré avant que l'exploration du second ne commence.
-
On distingue trois types de parcours selon l'ordre dans lequel le sous-arbre de gauche, le sous-arbre droit et la racine sont explorés.
balade et contours
Pour parcourir un arbre en profondeur, on se "balade" autour de l'arbre de la façon suivante (en commençant toujours par la gauche) :
Les flÚches numérotées en pointillé, qui sont représentées à cÎté des branches de l'arbre indiquent comment on se "balade" autour de l'arbre.
Ainsi on a les parcours successifs suivants:
- la flĂšche 1 indique que l'on va de r Ă a
- la flĂšche 2 indique que l'on va de a Ă c
- la flĂšche 3 indique que l'on va de c Ă h
- la flĂšche 4 indique que l'on va de h Ă c
etc.
đ Nous avons donc la "balade" r, a, c, h, c, a, d, i, d, j, l, j, d, a, r, b, e, k, e, b, f, b, r , que l'on appelera le "contours";
Source : https://math.univ-lyon1.fr/irem/IMG/pdf/parcours_arbre_avec_solutions-2.pdf
PremiÚre définition des trois parcours
On dĂ©finit trois parcours des sommets de lâarbre :
- Lâordre prĂ©fixe : on liste chaque sommet la premiĂšre fois quâon le rencontre dans la balade.
Chaque nĆud est visitĂ© avant que ses deux fils le soient.
On part de la racine, on visite le fils gauche (et Ă©ventuellement le fils gauche de celui-ci, etc.) avant de remonter et de redescendre vers le fils droit.
Cela donne ici : r, a, c, h, d, i, j, l, b, e, k, f
- lâordre suffixe (aussi appelĂ© postfixe en anglais) : on liste chaque sommet la derniĂšre fois quâon le rencontre.
Chaque nĆud est visitĂ© aprĂšs que ses deux fils le soient.
On visite le fils gauche, puis le fils droit, puis la racine
Cela donne ici : h, c, i, l, j, d, a, k, e, f , b, r
- lâordre infixe (plus compliquĂ©!): chaque nĆud est visitĂ© (listĂ©) aprĂšs son fils gauche mais avant son fils droit.
Si c'est une feuille il est donc listé (son fils gauche et son fils droit sont vides)
On visite le fils gauche, puis la racine, puis le fils droit
Cela donne ici : c, h, a, i ,d, l, j, r, k, e, b, f
đ Ces trois parcours sont naturellement rĂ©cursifs.
DeuxiÚme définition des trois parcours
đĄ On ajoute les fils fantĂŽmes manquants đ
On peut ainsi considĂ©rer quâon passe une fois Ă gauche de chaque nĆud (en descendant), une fois en dessous de chaque nĆud, une fois Ă droite de chaque nĆud (en remontant).
ordres préfixe, suffixe, infixe
- Ordre prĂ©fixe : lorsque l'on passe Ă gauche des nĆuds.
đ Regarder cette vidĂ©o : ordre prĂ©fixe
- Ordre suffixe : lorsque l'on passe Ă droite des nĆuds.
đ Regarder cette vidĂ©o : ordre suffixe
- Ordre infixe : lorsque l'on passe sous les noeuds
đ Regarder cette vidĂ©o : ordre infixe
Exercice 1
Donner le rendu de chaque parcours :
- Parcours en largeur
- Parcours préfixe
- Parcours infixe
- Parcours postfixe
largeur : 1 2 3 4 5 6 7 8 9
préfixe : 1 2 4 5 7 8 3 6 9
infixe : 4 2 7 5 8 1 3 9 6
postfixe : 4 7 8 5 2 9 6 3 1
Exercice 2
Donner le rendu de chaque parcours :
- Parcours en largeur
- Parcours préfixe
- Parcours infixe
- Parcours postfixe
largeur : 9 8 7 6 2 5 1 4 3
préfixe : 9 8 6 2 1 7 5 4 3
infixe : 6 8 1 2 9 7 4 5 3
postfixe : 6 1 2 8 4 3 5 7 9
Exercice débranché : Expérimentons ces trois parcours dans le cas concret d'un labyrinthe
Voici un labyrinthe :
Les cercles vides sont des nĆuds sans intersection, les cercles pleins sont des culs de sac, les carrĂ©s sont des intersections, l'entrĂ©e et la sortie sont marquĂ©es par un cercle plein dans un cercle vide.
Ce labyrinthe est parfait : chaque cellule est reliée à toutes les autres et, ce, de maniÚre unique
Contrairement au labyrinthe Ă©tudiĂ© dans le cours sur les graphe, celui-ci peut donc ĂȘtre reprĂ©sentĂ© par un arbre (il n'y a pas de cycle)
a. Faire la représentation de ce labyrinthe avec un arbre (sur feuille).
- Pour nommer un nĆud, il a Ă©tĂ© choisi de donner en premier le numĂ©ro de la colonne, et en deuxiĂšme le numĂ©ro de la ligne. Le nĆud d'entrĂ©e est donc le (0,4) et celui de sortie le (5,4).
- On part du nĆud (0,4) qui sera la racine de cet arbre. Lorsqu'il n'y a qu'un fils, on convient de le placer obligatoirement Ă gauche, et lorsqu'il y a une intersection, placez bien entendu Ă gauche le nĆud quand on va vers la gauche et Ă droite quand on va Ă droite.
- đ Pour simplifier, on notera 04 Ă la place (0, 4), 14 Ă la place de (1, 4) etc...
Dessin Ă faire sur votre feuille
b. Quelle est la hauteur de l'arbre ?
Solution
RĂ©ponse : 12
c. Quelle est la profondeur du nĆud (5,4) ? Qu'est-ce que cette profondeur reprĂ©sente dans notre problĂšme ?
Solution
RĂ©ponse : 9
C'est la longueur du plus court chemin vers la sortie.
d. Différents parcours en profondeur de cet arbre
đđ»
đAppelez votre professeur pour qu'il vous donne une version papier Ă complĂ©ter. Vous devez rĂ©aliser les trois parcours en profondeur vus (prĂ©fixe, suffixe et infixe) sur cet arbre.
Pour une question de mise en page quand il n'y a qu'un seul fils la flÚche est dessinée verticale (et non vers la gauche)
Vous pouvez aussi tĂ©lĂ©charger ce document ici : đ Arbres Ă complĂ©ter
Correction du parcours préfixe
Correction du parcours suffixe
Correction du parcours infixe
đQuel est le parcours qui donne le chemin le plus court pour trouver la sortie?
Solution
RĂ©ponse : parcours suffixe
đAurions-nous pu trouver un chemin plus rapide?
Solution
RĂ©ponse : il suffit de faire (0,4), (1,4), (2,4), (3,4), (4,4), (4,3), (4,2), (5,2), (5,3), (5,4)
Remarque
đ” La structure d'arbre n'est pas adaptĂ©e Ă la recherche de chemins, ou du plus court chemin. Nous verrons plus tard comment rĂ©soudre ce problĂšme avec des parcours de graphe.
V. ImplĂ©mentation des parcours en profondeurâïž
Une classe Arbre
Il faut absolument exécuter ce code pour pouvoir travailler ensuite.
# Tests
(insensible Ă la casse)(Ctrl+I)
(Ctrl+Clic pour inverser les colonnes)
1. Le parcours prĂ©fixeâïž
On donne le pseudo-code suivant :
fonction parcours_prefixe(arbre) : si arbre is not None : affiche arbre.valeur parcours_prefixe(arbre.gauche) parcours_prefixe(arbre.droit)
A vous de jouer 1
Compléter le code suivant, et le tester pour arbre1
(arbre ci-dessous)
Vérifier à la main que le résultat est bien correct.
flowchart TD
a(A)
b(B)
c(C)
d(D)
e(E)
f(F)
g( )
h( )
i(G)
a --- b
a --- c
b --- d
b --- e
c --- f
c --- g
d --- h
d --- i
linkStyle 5 stroke-width:0px;
linkStyle 6 stroke-width:0px;
style g opacity:0;
style h opacity:0;
# Tests
(insensible Ă la casse)(Ctrl+I)
(Ctrl+Clic pour inverser les colonnes)
Solution
# version simple avec print (renvoie None)
def parcours_prefixe(arbre) :
if arbre is not None :
print(arbre.valeur)
parcours_prefixe(arbre.gauche)
parcours_prefixe(arbre.droit)
# création de l'arbre :
arbre1 = Arbre("A")
arbre1.ajout_gauche("B")
arbre1.ajout_droit("C")
arbre1.gauche.ajout_gauche("D")
arbre1.gauche.ajout_droit("E")
arbre1.gauche.gauche.ajout_droit("G")
arbre1.droit.ajout_gauche("F")
# parcours
parcours_prefixe(arbre1)
A vous de jouer 2
Au lieu de simplement afficher les nĆuds visitĂ©s, nous allons en constituer la liste.
Compléter :
# Tests
(insensible Ă la casse)(Ctrl+I)
(Ctrl+Clic pour inverser les colonnes)
Retenir
préfixe : RGD (racine, sous-arbre gauche, sous-arbre droit)
Racine en 1er
Parcours préfixe
fonction parcours_prefixe(arbre): si arbre is not None : affiche arbre.valeur parcours_prefixe(arbre.gauche) parcours_prefixe(arbre.droit)
đ ou si on doit renvoyer une liste :
fonction parcours_prefixe(arbre): si arbre is None : renvoyer une liste vide sinon : renvoyer [arbre.valeur] + parcours_prefixe(arbre.gauche) + parcours_prefixe(arbre.droit)
2. Le parcours suffixeâïž
On donne le pseudo-code suivant :
fonction parcours_suffixe(arbre) : si arbre is not None : parcours_suffixe(arbre.gauche) parcours_suffixe(arbre.droit) affiche arbre.valeur
A vous de jouer 3
Compléter le code suivant, et le tester pour arbre1
(arbre ci-dessous)
Vérifier à la main que le résultat est bien correct.
flowchart TD
a(A)
b(B)
c(C)
d(D)
e(E)
f(F)
g( )
h( )
i(G)
a --- b
a --- c
b --- d
b --- e
c --- f
c --- g
d --- h
d --- i
linkStyle 5 stroke-width:0px;
linkStyle 6 stroke-width:0px;
style g opacity:0;
style h opacity:0;
# Tests
(insensible Ă la casse)(Ctrl+I)
(Ctrl+Clic pour inverser les colonnes)
Solution
def parcours_suffixe(arbre):
if arbre is not None:
parcours_suffixe(arbre.gauche)
parcours_suffixe(arbre.droit)
print(arbre.valeur)
A vous de jouer 4
Au lieu de simplement afficher les nĆuds visitĂ©s, nous allons en constituer la liste.
Compléter :
# Tests
(insensible Ă la casse)(Ctrl+I)
(Ctrl+Clic pour inverser les colonnes)
Retenir
suffixe : GDR (sous-arbre gauche, sous-arbre droit, racine)
Racine en dernier
Parcours suffixe
fonction parcours_suffixe(arbre) : si arbre is not None : parcours_suffixe(arbre.gauche) parcours_suffixe(arbre.droit) affiche arbre.valeur
đ ou si on doit renvoyer une liste :
fonction parcours_suffixe(arbre) : si arbre is None : renvoyer une liste vide sinon : renvoyer parcours_suffixe(arbre.gauche) + parcours_suffixe(arbre.droit) + [arbre.valeur]
3. Le parcours infixeâïž
On donne le pseudo-code suivant :
fonction parcours_infixe(arbre) : si arbre is not None : parcours_infixe(arbre.gauche) affiche arbre.valeur parcours_infixe(arbre.droit)
A vous de jouer 5
Compléter le code suivant, et le tester pour arbre1
(arbre ci-dessous)
Vérifier à la main que le résultat est bien correct.
flowchart TD
a(A)
b(B)
c(C)
d(D)
e(E)
f(F)
g( )
h( )
i(G)
a --- b
a --- c
b --- d
b --- e
c --- f
c --- g
d --- h
d --- i
linkStyle 5 stroke-width:0px;
linkStyle 6 stroke-width:0px;
style g opacity:0;
style h opacity:0;
# Tests
(insensible Ă la casse)(Ctrl+I)
(Ctrl+Clic pour inverser les colonnes)
Solution
def parcours_infixe(arbre):
if arbre is not None:
parcours_infixe(arbre.gauche)
print(arbre.valeur)
parcours_infixe(arbre.droit)
A vous de jouer 6
Au lieu de simplement afficher les nĆuds visitĂ©s, nous allons en constituer la liste.
Compléter :
# Tests
(insensible Ă la casse)(Ctrl+I)
(Ctrl+Clic pour inverser les colonnes)
Retenir
infixe : GRD (sous-arbre gauche, racine, sous-arbre droit)
Racine au milieu
Parcours infixe
fonction parcours_infixe(arbre) : si arbre is not None : parcours_infixe(arbre.gauche) affiche arbre.valeur parcours_infixe(arbre.droit)
đ ou si on doit renvoyer une liste :
fonction parcours_infixe(arbre) : si arbre is None : renvoyer une liste vide sinon : renvoyer parcours_infixe(arbre.gauche) + [arbre.valeur] + parcours_infixe(arbre.droit)
4. Quel parcours choisir ?âïž
Retenir
Nous avons vu que le parcours en largeur Ă©tait pertinent pour avoir des renseignements plutĂŽt par niveau, utile par exemple si on veut connaĂźtre les personnes d'une mĂȘme gĂ©nĂ©ration.
Le parcours préfixe, parcourt l'arbre en profondeur plutÎt de maniÚre descendante, et le suffixe plutÎt de maniÚre ascendante.
Quel est l'intĂ©rĂȘt du parcours infixe ?
Nous allons voir cela dans le paragraphe suivant. đ
5. Parcours infixe sur un arbre binaire de recherche.âïž
Exemple
Le tri du bijoutier (d'aprĂšs un sujet de l'APMEP)
Considérons le problÚme du bijoutier voulant trier par grosseur un tas de diamants :
pour faire cette opĂ©ration il se sert dâun tamis ce qui lui permet de sĂ©parer le tas initial
en deux, et il recommence avec de nouveaux tamis pour chaque tas. On le comprend
facilement lâefficacitĂ© du tri est fonction des trames des tamis utilisĂ©s.
Nous allons utiliser une idée similaire pour créer un arbre binaire, et pour construire les
algorithmes permettant de le parcourir.
Mieux quâun grand discours montrons la construction de lâarbre correspondant aux donnĂ©es 7, 3, 1, 8, 6.
Nous allons placer le premier élément 7 à la racine d'un arbre.
Principe du tamis : pour n'importe quel noeud, tous les noeuds se trouvant dans son sous-arbre gauche ont une valeur inférieure, et tous ceux se trouvant dans le sous arbre droit ont une valeur supérieure. Le sous arbre gauche correspond à ce qui est passé dans les trous du tamis, et le sous-arbre droit à ce qui est resté dans le tamis.
- 3 < 7 donc 3 est fils gauche de 7
- 1 < 3 donc 1 est fils gauche de 3
- 8 > 7 donc 8 est fils droit de 7. (on ne peut pas mettre 8 comme fils droit de 3, car sinon 8 serait dans le sous-arbre gauche de 7, ce qui est impossible car ce sous-arbre ne doit contenir que des nombres inférieurs à 7).
- 6 > 3 et 6 < 7 donc 6 est fils droit de 3.
On obtient l'arbre suivant :
# Tests
(insensible Ă la casse)(Ctrl+I)
(Ctrl+Clic pour inverser les colonnes)
Nous venons de créer un arbre binaire de recherche
Nous reviendrons plus tard en détail sur cette structure de données.
A vous de jouer 7
Reprenons cet exemple et implémentons le avec la classe Arbre du début.
Compléter ci-dessous. Que constatez-vous ?
# Tests
(insensible Ă la casse)(Ctrl+I)
(Ctrl+Clic pour inverser les colonnes)
Solution
# Compléter ci-dessous
abr = Arbre(7)
abr.ajout_gauche(3)
abr.gauche.ajout_gauche(1)
abr.gauche.ajout_droit(6)
abr.ajout_droit(8)
đ
đ A retenir
Le parcours infixe sur un arbre binaire de recherche trie les noeuds de cet arbre.
đ C'est un des trĂšs grands intĂ©rĂȘts des arbres binaires de recherche, et du parcours infixe.
VI. TP finalâïž
â Avant de commencer
Vous devez travailler sur Basthon
TĂ©lĂ©charger dans le mĂȘme dossier :
-
đ Fichier
module_tree_2021.py
: "Clic droit", puis "Enregistrer la cible du lien sous" -
đ TD
parcours_arbre_labyrinthe_parfait_sujet_2023.ipynb
: "Clic droit", puis "Enregistrer la cible du lien sous"
đ La correction est arrivĂ©e ...
TĂ©lĂ©charger dans le mĂȘme dossier :
-
đ Fichier
module_tree_2021.py
: "Clic droit", puis "Enregistrer la cible du lien sous" -
đ Correction du TD
parcours_arbre_labyrinthe_parfait_corr_2023_v2.ipynb
: "Clic droit", puis "Enregistrer la cible du lien sous"
VII. â Bilanâïž
Parcours en largeur
aVoir <- fileVide enfiler la racine vus = listeVide (si on doit renvoyer le parcours) tant que aVoir non vide : a <- defiler() visiter a (ajout dans une liste vus, ou affichage) si a.filsG not None: enfiler a.filsG si a.filsD not None: enfiler a.filsD
Parcours préfixe
def prefixe(arbre) : if arbre is None : return [] else : return [arbre.valeur] + prefixe(arbre.filsG) + prefixe(arbre.filsD)
Parcours suffixe
def suffixe(arbre) : if arbre is None : return [] else : return suffixe(arbre.filsG) + suffixe(arbre.filsD) + [arbre.valeur]
Parcours infixe
def infixe(arbre) : if arbre is None : return [] else : return infixe(arbre.filsG) + [arbre.valeur] + infixe(arbre.filsD)
VIII. VidĂ©os pour approfondirâïž
Parcours préfixe
Parcours suffixe
đ Le parcours suffixe se fait de façon analogue.
Parcours infixe
đł CrĂ©ditsâïž
Jean-Louis Thirot , Mireille Coilhac, Valérie Mousseaux, Gilles Lassus
# Tests
(insensible Ă la casse)(Ctrl+I)
(Ctrl+Clic pour inverser les colonnes)