Le parcours en profondeur itératif n'est pas le récursif
Exercice · niveau 2 · informatique (MP2I/MPI), chapitre 19 — Parcours de graphes et plus courts chemins
Énoncé
On remplace la récursion par une pile explicite. Les deux versions visitent-elles les sommets dans le même ordre ?
let profondeur_pile g s =
let vu = Array.make (Array.length g) false and pile = ref [s] in
vu.(s) <- true;
while !pile <> [] do
let u = List.hd !pile in pile := List.tl !pile;
traiter u;
List.iter (fun v -> if not vu.(v) then begin vu.(v) <- true; pile := v :: !pile end) g.(u)
doneCorrigé
Non, et l'écart est spectaculaire. Sur le graphe à huit sommets du premier exercice de ce chapitre :
recursif : 0 1 3 6 4 5 2 7
iteratif avec la pile ci-dessus : 0 2 5 6 7 4 3 1
Les deux visitent bien tous les sommets — ce sont deux parcours en profondeur valides — mais dans des ordres presque opposés.
Deux causes, et il faut les corriger toutes les deux.
- La pile inverse l'ordre des voisins. On empile puis ; on dépile d'abord. La version récursive, elle, appelle d'abord. Empiler les voisins à l'envers corrige ce point.
- Le moment du marquage diffère. La récursion marque quand elle le visite ; la pile ci-dessus le marque quand elle l'empile, donc bien avant. Un sommet atteignable par deux chemins sera donc marqué depuis le mauvais.
Mesure de la correction partielle : en empilant seulement les voisins à l'envers, on obtient 0 1 3 6 5 7 4 2 — toujours différent du récursif. Il faut les deux corrections :
(* Parcours en profondeur iteratif REPRODUISANT exactement l'ordre recursif.
Precondition : 0 <= s < Array.length g.
Complexite : Theta(n + m) en temps, O(m) en memoire (la pile). *)
let profondeur_fidele g s =
let vu = Array.make (Array.length g) false and pile = ref [s] in
while !pile <> [] do
let u = List.hd !pile in pile := List.tl !pile;
if not vu.(u) then begin (* on MARQUE AU RETRAIT, comme la recursion *)
vu.(u) <- true;
traiter u;
List.iter (fun v -> if not vu.(v) then pile := v :: !pile)
(List.rev g.(u)) (* et on empile les voisins A L'ENVERS *)
end
done
Mesure : 0 1 3 6 4 5 2 7, identique au récursif — vérifié aussi sur un second graphe orienté, pour ne pas conclure d'un seul cas.
Le prix à payer, et il faut le savoir. Cette version marque au retrait : elle retombe donc exactement dans le défaut de l'exercice précédent, et sa pile peut contenir éléments. Les trois versions se rangent ainsi :
| Version | ordre rendu | taille de la pile |
|---|---|---|
| pile, marquage à l'empilement | différent du récursif | |
| pile, marquage au retrait | identique au récursif | |
| pile de couples (sommet, position) | identique au récursif |
La troisième version est la simulation exacte de la récursion : on empile non pas les voisins, mais le sommet accompagné de l'indice où l'on en est dans sa liste d'adjacence — c'est-à-dire le bloc d'activation du chapitre chap:memoire, reconstitué à la main. Elle a les deux qualités, et un code deux fois plus long. Quand l'ordre n'importe pas — accessibilité, composantes connexes —, on prend la première ; quand il importe — tri topologique, numérotation préfixe et suffixe —, on écrit la récursion, et l'on ne descend à la pile explicite que si la profondeur menace de la faire déborder.
Les autres exercices de ce chapitre Le cours du chapitre
Un blocage sur cet exercice ? Le tuteur d'Adloun guide par questions, sans donner la réponse.