Solutions du TD-7, Promenade dans le labyrinthe
Étaient fournies, les classes
Sommet, ListeSommets, Laby
et AnimLaby.
1 Parcours
1.1 En profondeur
Source Profondeur.
C'est du cours. La seule difficulté est de bien gérer l'animation et
de comprendre que les sommets sont indexés par une paire d'entiers.
Source Largeur, qui a besoin du source File des
files de sommets.
Pour les détails, voir la
section
5.6 du poly.
2 Le chemin le plus long
2.1 les chemins
La solution est la classe Tous.
Si on fait abstraction de l'animation graphique et du calcul de la
plus grande longueur on obtient cette méthode de parcours,
on obtient une méthode sortir qui compte les chemins vers la sortie :
1 static int sortir (Sommet pere, Sommet s, boolean [] [] vu) {
if (vu[s.i][s.j])
return 0;
vu[s.i][s.j] = true;
5
if (s.equals(laby.sortie)) {
vu[s.i][s.j] = false ;
return 1 ;
}
10 int r = 0 ;
for (ListeSommets l = laby.voisins[s.i][s.j] ;
l != null ; l = l.suivant) {
int t = sortir (s, l.cont, vu) ;
r += t ;
15 }
vu[s.i][s.j] = false ;
return r ;
}
Comparons avec la méthode de parcours de la classe
Profondeur, pareillement simplifiée en une méthode
sortir qui renvoie true si on peut atteindre la
sortie et false sinon :
1 static boolean sortir (Sommet pere, Sommet s, boolean [] [] vu) {
if (vu[s.i][s.j])
return false;
vu[s.i][s.j] = true;
5
if (s.equals(laby.sortie))
return true ;
for (ListeSommets l = laby.voisins[s.i][s.j] ;
10 l != null ; l = l.suivant) {
boolean t = sortir (s, l.cont, vu) ;
if (t) {
return true;
}
15 }
return false;
}
On remarquera que seule différence est que, dans le cas de du parcours
de tous les chemins, on remet vu[s.i][s.j] à false
avant de retourner, et que l'on ne retourne pas de l'intérieur de la
boucle de visite des voisins.
Ces petites modifications changent totalement le coût du programme, qui
explose littéralement dans le cas de la recherce de tous les chemins et
est borné par le nombre de cases dans le cas de la recherche d'un
chemin.
Par conséquent lorsque l'on cherche seulement à savoir si le
labyrinthe a une solution, mieux vaut tester
``Profondeur.sortir(...)
'' que ``Tous.sortir(...) >= 0
''.
Pour ce qui est du source complet de Tous,
vous noterez aussi l'utilisation d'une variable global len
pour calculer la longueur des chemins, variable qui est incrémentée
avant l'appel récursif et décrémentée après.
Comme l'indique l'énoncé, il s'agit de trier les listes de voisins, en
mettant ceux qui éloigent de la sortie en premier.
Une façon objet de le faire est d'étendre la classe Laby en
une classe SLaby qui contient les méthodes de tri
(un bête tri par insertion, suffisant pour les petites
listes (un sommet a au plus quatre voisins).
Le constructeur de cette classe, se contente ensuite d'appeler d'abord
le constructeur de sa classe parente (mot-clé super)
et de trier les voisins.
class SLaby extends Laby {
SLaby(BufferedReader in) {
super(in);
trie() ;
}
void trie () {
...
}
}
De même, la classe Promenade étend
la classe Profondeur dont elle profite de la méthode de
parcours
sortir.
import java.io.*;
class Promenade extends Profondeur {
public static void main (String args []) {
laby =
new SLaby (new BufferedReader (new InputStreamReader(System.in)));
AnimLaby.start(laby);
int length = sortir(null, laby.entree,
new boolean [laby.hauteur] [laby.largeur]) ;
if (length < 0)
System.out.println("Pas de solution...");
else
System.out.println("Longueur : " + length) ;
}
}
On peut aussi programmer ceci plus classiquement en écrivant une
classe
Promenade qui est une copie de Profondeur, augmentée
des méthodes de tri. L'avantage de la programmation objet est que le
code de sortir est commun aux deux classes, alors qu'il est
dupliqué avec la solution classique.
Ça devient plus sioux, car il y a pas mal de manipulations de chemins.
Pour faciliter la recherche des détours, le plus long chemin à un
instant donné est représenté par deux structures de données :
une liste de ses sommets pris dans l'ordre, qui est gérée par des
variables locales, et un tableau global d'entiers
path,
qui est tel :
-
Si le sommet s est dans le chemin, alors
path[s.i][s.j] vaut la longueur du chemin
de l'entrée à s.
- Sinon, path[s.i][s.j] vaut
-1 par convention.
À partir de là, je propose deux solutions, l'une Detour
calcule les détours par un recherche en profondeur d'abord
(zyva et zyvaRec),
suivant une heuristique spécifique (les voisins dont les voisins sont
des cases vides en premier) ; l'autre DetourBound effectue
une recherche exhaustive (zyva et zyvaRec)
de tous les détours à partir d'un sommet
donné et selectionne l'un des détours possible parmi les plus longs.
Dans les deux cas les chemins sont détournés de
l'entrée vers la sortie par la méthode betterRec, qui est
appelée jusqu'à plus soif par la méthode better.
2.4 Un peu de culture
On peut se demander s'il existe un algoritme exact et raisonnable
(la classe Tous n'est pas raisonnable) qui calcule la plus
longue promenade dans le labyrinthe.
En effet, il existe bien un algorithme raisonnable qui trouve la
promenade la plus courte (parcours avec une file).
Il semble bien que non, car la
recherche du plus long chemin dans un graphe est un problème
particulièrement coûteux à résoudre. C'est un problème NP-complet,
c'est à dire qu'il existe un algorithme en temps polynomial (une
définition de raisonnable) pour le
résoudre si et seulement si il en existe un pour résoudre
une serie de problèmes classiques, dont la satisfiabilité des formules
booléennes, pour lequels on sèche depuis un
demi-siècle.
Voir
http://www.nada.kth.se/~viggo/wwwcompendium/wwwcompendium.html
et en particulier
ici.
La solution est la classe source Bi, qui suit exactement
l'article.
(Les sources Arc et Pile étaient donnés.)
La seule subtilité concerne le statut de la racine de chaque arbre de
recouvrement.
Cette racine n'est un point d'articulation que si l'arbre de
recouvrement a deux fils ou plus à la racine.
Soit dans le cas d'affichage d'une composante biconnexe,
quand le père u du sommet en cours d'examen s est
inexistant (u == null) et il reste au moins un voisin
non-encore parcourru (méthode voisinPasVu).
Ce document a été traduit de LATEX par
HEVEA.