Title: Algorithmique et structures de donnes en C
1Algorithmique et structures de données en C
Cours 6
Les arbres (3) Parcours
É. Delozanne, L2-S4-prog4, Paris
5 Elisabeth.Delozanne_at_math-info.univ-paris5.fr htt
p//www.math-info.univ-paris5.fr/delozanne/
2Parcourir un arbre ?
- parcours
- examiner les nuds dun arbre pour effectuer un
traitement - 2 types de parcours classiques sur les arbres
binaires
en profondeur à main gauche (récursif par nature)
en largeur (itératif par nature)
3 Parcours d'arbres binaires récursifs/itératifs
- Plan du cours
- parcours en profondeur à main gauche
- algorithme récursif général
- cas particuliers (parcours préfixe, infixe,
postfixe) - algorithme itératif général utilise une pile
- application calcul de la hauteur d'un arbre
- simplification dans le cas d'un parcours préfixe
- parcours en largeur (par niveaux, hiérarchique)
- utilise une file
- récursivité et itération
-
-
4Objectifs du cours (et TD)
- Mise en uvre en C dalgorithmes (un peu)
complexes - Culture informatique de base sur les algorithmes
récursifs et itératifs et sur la compilation - Vue abstraite sur les parcours darbres pour
savoir les écrire plus facilement - Compétence à acquérir
- Savoir écrire rapidement et sans erreur les
algorithmes récursifs classiques de parcours
darbres binaires - Afficher, lire, construire, hauteur, taille,
libérer la mémoire, tests etc. - Savoir programmer un parcours en largeur
5Parcours en profondeur à main gauche
- chemin qui descend toujours le plus à gauche
possible - chaque nud est rencontré 3 fois
- à la descente avant la visite du sous-arbre
gauche - on applique au nud le traitement 1
- Après la visite du sous-arbre gauche et avant de
descendre dans le sous-arbre droit - on applique au nud le traitement 2
- (et dernière fois) quand les deux sous-arbres ont
été parcourus, en remontant à droite - on applique au nud le traitement 3
- si l'arbre est vide on lui applique un traitement
appelé terminaison
6Parcours récursif en profondeur à main gauche
- Algorithme Parcours (A)
- si A est vide
- alors terminaison(A)
- sinon
- traitement1 (A)
- Parcours(sous-arbreGauche(A)
- traitement2 (A)
- Parcours(sous-arbreDroit(A)
- traitement3 (A)
Exemple void ArbinTracer(ARBIN A) if
(ArbinVide(A)) / terminaison / printf("()
") else ElementAfficher(ArbinEtiquette(A)
) / traitement 1 / ArbinTracer(ArbinGauche
(A)) ElementAfficher(ArbinEtiquette(A))
/ traitement 2 / ArbinTracer(ArbinDroit(A)
) ElementAfficher(ArbinEtiquette(A)) /
traitement 3 /
7Cas particuliers
- Lorsque le noeud est traité une seule fois
- Parcours préfixe (Racine Gauche Droit)
- le nud racine est traité au premier passage
avant le parcours des sous-arbres - Parcours infixe ou symétrique (Gauche Racine
Droit) - le nud racine est traité au second passage
- après le parcours du sous-arbre gauche
- et avant le parcours du sous-arbre droit
- Parcours postfixe (Gauche Droit Racine )
- le nud racine est traité en dernier après le
parcours des sous-arbres
8Parcours préfixe (R G D)
- le nud racine est traité au premier passage
avant le parcours des sous-arbres - ParcoursPréfixe(A)
- si A est vide
- alors terminaison(A)
- sinon
- traitement (A)
- ParcoursPréfixe(sous-arbreGauche de A)
- ParcoursPréfixe(sous-arbreDroit de A)
9Exemple RGD
- Afficher les étiquettes d'un arbre (ordre
préfixe) - terminaison afficher arbre vide () - juste
pour voir - traitement 1 afficher l'étiquette
- void ArbinAfficherPref(ARBIN A)
- if (ArbinVide(A))
- printf("() ") / terminaison /
- else
- ElementAfficher(ArbinEtiquette(A)) /
traitement 1 / - ArbinAfficherPref(ArbinGauche(A))
- ArbinAfficherPref(ArbinDroit(A))
-
-
10Parcours infixe ou symétrique (G R D)
- le nud racine est traité au second passage
après le parcours du sous-arbre gauche et avant
le parcours du sous-arbre droit - ParcoursInfixe(A)
- si A est vide
- alors terminaison(A)
- sinon
- ParcoursInfixe(sous-arbreGauche de A)
- traitement (A)
- ParcoursInfixe(sous-arbreDroit de A)
11Exemple GRD
- Afficher les étiquettes d'un arbre (ordre infixe)
- terminaison afficher arbre vide ()
- traitement 2 afficher l'étiquette
- void ArbinAfficherInf(ARBIN A)
- if (ArbinVide(A))
- printf("() ") / terminaison /
- else
- ArbinAfficherInf(ArbinGauche(A))
- ElementAfficher(ArbinEtiquette(A))
- / traitement 2 /
- ArbinAfficherInf(ArbinDroit(A))
-
-
12Parcours postfixe (G D R)
- le nud racine est traité au troisième passage
après le parcours des sous-arbres gauche et droit - ParcoursPostfixe(A)
- si A est vide
- alors terminaison(A)
- sinon
- ParcoursPostfixe(sous-arbreGauche de A)
- ParcoursPostfixe(sous-arbreDroit de A)
- traitement (A)
13Exemples GDR
- Afficher les étiquettes d'un arbre (ordre
postfixe) - terminaison afficher arbre vide
- traitement 3 afficher l'étiquette
- code C
- void ArbinAfficherPost(ARBIN A)
- if (ArbinVide(A))
- printf("() ") / terminaison /
- else
- ArbinAfficherPost(ArbinGauche(A))
- ArbinAfficherPost(ArbinDroit(A))
- ElementAfficher(ArbinEtiquette(A)) /
traitement 3 / -
-
14"Dérécursiver"
- dérécursiver
- transformer un algorithme récursif en algorithme
itératif - pour des raisons d'efficacité (économie de temps
et de mémoire) on peut être amené à
"dérécursiver" certains algorithmes - on le plus souvent les compilateurs
- méthodes générales pour supprimer
- un appel récursif terminal
- utilise une boucle tant que
- un appel récursif non terminal
- utilise une pile
15Parcours itératif en profondeur à main gauche
- idée une pile mémorisant le nud courant et la
direction future - descente (à gauche du nud, 1ier passage)
- effectuer le traitement 1 sur la racine
- empiler le nud et la direction future (descendre
à dr., 2ième passage) - descendre à gauche sur le sous-arbre gauche
- remontée gauche (2ième passage)
- effectuer le traitement 2 sur la racine
- empiler le nud et la direction future (remonter
à droite, 3ième passage) - descendre à gauche sur le sous-arbre droit
- remontée droite (3ième passage)
- effectuer le traitement 3 sur la racine
- dépiler le nud et le sens de la visite sur ce
nud
16Parcours itératif en profondeur à main gauche
- A l'arbre à parcourir
- créer une pile vide P , direction descente
gauche fini A non vide - tant que (pas fini)
- Si A est vide
- alors
- terminaison
- Si P est vide alors fini sinon (A et direction)
dépiler - si (pas fini) selon direction
- direction descente gauche
- traitement 1, empiler (A, desc. dr),
- A ss-arbGauche (A) (desc. à g. sur le
sous-arbre gauche) - direction descente droite
- traitement 2, empiler (A, montée. dr),
- A ss-arbDroit (A) , direction descente
gauche(descendre à gauche sur le sous-arbre
droit) - direction montée droite
- traitement 3,
- Si P est vide alors fini sinon (A et direction)
dépiler - détruire la pile P
17Exemple calcul de la hauteur d'un arbre
- hauteur(A) (algorithme récursif)
- si A est vide retourner -1
- sinon
- hg Hauteur(sous-arbreGauche(A)
- hd Hauteur(sous-arbreDroit(A)
- si hg gt hd
- retourner hg 1
- sinon retourner hd 1
18Calcul itératif de la hauteur d'un arbre binaire
- A arbre à parcourir, h -1, max -1,
- créer une pile vide P , direction descente
gauche - répéter jusqu'à Pile vide et direction montée
droite - si A est vide alors
- direction montée droite
- si h gt max alors max h
- si (Pile non vide ou direction ? montée droite)
alors selon direction - direction descente gauche
- h , empiler (A, desc. dr),
- A ss-arbGauche (A)
- direction descente droite
- h, empiler (A, montée. dr),
- A ss-arbDroit (A) , direction descente
gauche - direction montée droite
- h--, dépiler le nud A et la direction
- détruire P et retourner max
19Codage en C
- idée géniale utiliser
- le TDA PILE (cours 3) et le TDA ARBRE BINAIRE
(cours 5) - problème
- le TDA ARBRE BINAIRE utilise un TDA ELEMENT
incarné par des entiers (ou des caractères) - le TDA PILE utilise lui aussi un TDA ELEMENT mais
incarné par des couples (arbres, position) - le souk !
- un même identificateur (ELEMENT) désigne deux
types différents - solution
- en C bricolo
- en C les template (polymorphisme
paramétrique) - en Smalltalk, Java polymorphisme dhéritage
20Simplifications de l'algorithme général
- exemple Parcours préfixe itératif (à la
Sedgewick - A arbre à parcourir
- créer une Pile vide P
- si l'arbre A est non vide empiler A
- tant que P est non vide répéter
- dépiler dans A
- traiter (A)
- si le sous-arbre droit est non vide, l'empiler
- si le sous-arbre gauche est non vide, l'empiler
- détruire (P)
21 Parcours d'arbres binaires récursifs/itératifs
- Plan du cours
- parcours en profondeur à main gauche
- algorithme récursif général
- cas particuliers (parcours préfixe, infixe,
postfixe) - algorithme itératif général utilise une pile
- application calcul de la hauteur d'un arbre
- simplification dans le cas d'un parcours préfixe
- parcours en largeur (par niveaux, hiérarchique)
- utilise une file
- récursivité et itération
-
-
22Parcours en largeur (hiérarchique, par niveau)
- stratégie pas du tout récursive
- traitement des nuds par niveau traiter les
frères avant les fils - dans la représentation graphique
- 1. de gauche à droite
- 2. de haut vers le bas
- utilisation d'une file
- on traite un nud
- on fait entrer dans la file
- dabord son fils gauche
- puis son fils droit
23Algorithme
- ParcoursEnLargeur (A)
- créer une file vide F
- si A est non vide alors
- entrer A dans F
- tant que F non vide
- A sortir(F)
- traiter (A)
- si ss-arbGauche(A) non vide l'entrer dans F
- si ss-arbDroit(A) non vide l'entrer dans F
- FinSi
- détruire F
24Affichage en largeur
- void ArbinAfficherLargeur(ARBIN A)
- FFILE F FileCreer(20) ELTSPCL elt
- if ( ! ArbinVide(A) )
- elt EltSpclInit(A,1) FileEntrer(elt, F)
- while ( ! FileVide (F) )
- A EltSpclArbre(FileSortir(F))
- ElementAfficher(ArbinEtiquette(A))
- if( ! ArbinVide(ArbinGauche(A)) )
- elt EltSpclInit(ArbinGauche(A),1)
FileEntrer(elt, F) -
- if( ! ArbinVide(ArbinDroit(A)) )
- elt EltSpclInit(ArbinDroit(A),1)FileEntrer(e
lt, F) -
-
-
- FileDetruire(F)
25 Parcours d'arbres binaires récursifs/itératifs
- Plan du cours
- parcours en profondeur à main gauche
- algorithme récursif général
- cas particuliers (parcours préfixe, infixe,
postfixe) - algorithme itératif général utilise une pile
- application calcul de la hauteur d'un arbre
- simplification dans le cas d'un parcours préfixe
- parcours en largeur (par niveaux, hiérarchique)
- utilise une file
- récursivité et itération
-
-
26Y'avait longtemps...
- factorielle version récursive
- long factorielle (int n)
- if (n lt 0) return 1
- else return n factorielle (n - 1)
-
- factorielle version itérative
- long factorielle (int n)
- int r 1
- while ( n gt 1) r r n n --
- return r
-
27Et sa copine ...fibonacci
- fibonacci version récursive
- long fibonacci (int n)
- if (n lt 1) return 1
- else return fibonacci (n - 1) fibonacci (n -
2) -
- fibonacci version itérative
- long fibonacci (int n)
- int u, v u v 1
- for (i 3 i lt n i) u u v v
u - v - return u
28Récursif versus itératif
- sur ces 2 exemples triviaux la solution itérative
est meilleure - certains compilateurs astucieux remplacent
- l'appel récursif terminal par une boucle tant que
- un appel non terminal par un algorithme itératif
utilisant une pile - certains algorithmes sont naturellement
récursifs - 1) algorithmes récursifs de type diviser pour
résoudre - 2 appels récursifs traitant chacun en gros la
moitié des données sans calcul redondant - 2) algorithmes récursifs de type combiner pour
résoudre - commencer par résoudre des cas triviaux
- puis en les combinant résoudre des cas complexes
29Bilan
- un algorithme récursif peut être transformé en
algorithme itératif (ne le faire que si on voit
que l'algorithme "patine" ou besoin impérieux) - un programme itératif n'est pas toujours plus
efficace qu'un programme récursif - la récursivité est centrale
- en informatique dite théorique pour distinguer
les problèmes (problèmes calculables) qu'une
machine peut résoudre en temps fini - en IA et en recherche opérationnelle pour
résoudre des problèmes complexes - en programmation pour traiter de manière simple
de nombreux problèmes sur les arbres binaires
(recherche, tri etc.)
30Retenir
- Un algorithme itératif nest pas forcément plus
rapide quun algorithme itératif - Certains algorithmes sont mieux traités par des
algorithmes itératifs - Dautres sont mieux traités par des algorithmes
récursifs
31Auto-évaluation cours 6
- Quest-ce que parcourir un arbre ?
- Quels sont les parcours classiques sur les arbres
? - Les algorithmes itératifs sont-ils toujours plus
efficaces que les algorithmes récursifs ?
Réciproque ? - Donner des exemples de parcours en profondeur à
main gauche ? - Même question pour le parcours hiérarchique
- Écrire en C les parcours classiques sur les
arbres binaires étudiés en cours, en TD