Promenades dans un labyrinthe

Cette semaine, au lieu de chercher le plus cours chemin dans un graphe, nous allons nous intéresser au plus long chemin. Indépendamment de l'aspect ludique, c'est en fait un problème difficile qu'on aurait bien du mal à résoudre de façon raisonnable sur un gros graphe comme celui du métro. Pour simplifier, nous allons donc considérer des labyrinthes.

Les labyrinthes

On considère des labyrinthes rectangulaires de taille hauteur x largeur. L'entrée du labyrinthe est en haut à gauche, la sortie en bas à droite. Un labyrinthe n'est rien d'autre qu'un graphe. Par exemple, le labyrinthe
correspond au graphe suivant :

Ce labyrinthe, et d'autres, sont disponibles sous forme de fichiers textes. En voici la liste :

Labyrinthes taille Plus court chemin Plus long chemin
petit 4 x 8 1218
grand16 x 27 41>=337
a20 x 25 43>=391
b10 x 12 2882
c50 x 50 98>=2036
d60 x 60 118>=1944
zoo7 x 22 27107
foo7 x 22 27153
bar7 x 22 27153
Il n'est pas très difficile de convertir ces fichiers textes en représentation interne. L'opération inverse peut permettre de visualiser des solutions.

Si ces opérations vous semblent trop fastidieuses, vous pouvez utiliser une solution toute faite. Attention, cette solution n'utilise pas exactement les mêmes structures de données qu'au dernier TP, et il est tout à fait possible qu'elle ne vous convienne pas. N'hésitez pas à la modifier pour manipuler les structures qui vous plaisent.

Première promenade : chercher la sortie

La première fois qu'on visite un labyrinthe, c'est souvent pour s'amuser à trouver la sortie. Quand on le fait de façon systématique (méthode de la main gauche, qui consiste à toujours tourner à gauche sans revenir sur ses pas), on visite en fait le labyrinthe par un parcours en profondeur.

Écrire cette première promenade.

On peut noter que cet algorithme est particulièrement inefficace quand le labyrinthe n'a pas de sortie. Ce n'est d'ailleurs pas la seule situation où cet algorithme est lent. Essayez d'évaluer sa complexité.

Deuxième promenade : le visiteur pressé

Cette fois, on essaye de sortir du labyrinthe le plus vite possible. L'algorithme le plus efficace est très similaire à celui vu au TP précédent. La seule différence est la file utilisée lors du parcours qui n'a plus besoin d'être une file de priorité. On peut se contenter d'une file ordinaire (implémentée par un pointeur sur le début d'une liste et un pointeur sur la fin) dans laquelle on ajoute toujours les éléments en queue.

Écrire la recherche du plus court chemin pour les labyrinthes.

Troisième promenade : le touriste

Les vraies promenades, ça prend du temps. Dans cet esprit, nous allons chercher le plus long chemin possible (sans cycle, évidemment) entre l'entrée et la sortie.

Première solution : recherche exhaustive

Le parcours en profondeur explore tous les chemins possibles jusqu'à en trouver un qui arrive à la sortie. Il est facile de le modifier pour qu'il ne s'arrête pas à la première solution mais continue en gardant la solution la plus longue en mémoire.

Tester cette solution.

Cette solution est bien entendu très coûteuse. On peut l'améliorer en évitant une partie du travail inutile. Il suffit de reconnaître si une configuration donnée a une solution ou non. Pour cela, la recherche de chemin le plus court à partir de la configuration est assez efficace.

Modifier cette solution en cherchant à chaque noeud si il existe un chemin vers la sortie (et si non, ne pas faire les recherches dans cette configuration).

Peut-on faire mieux ?

Puisqu'il existe un algorithme pour trouver le chemin le plus court en un temps raisonnable, on peut se demander si un tel algorithme existe pour le chemin le plus long. En fait, la réponse reste ouverte. Ce problème appartient à la classe des problèmes NP-complets. C'est à dire que si il existe un algorithme pour le résoudre en temps polynomial dans le cas le pire, alors il en existe un aussi pour une série de problèmes classiques (satisfiabilité des formules booléennes, voyageur de commerce...) qu'on ne sait pas à l'heure actuelle résoudre en temps polynomial (notez qu'on ne sait pas non plus si c'est impossible).

Dans de tels cas, on peut trouver des heuristiques qui vont donner le bon résultat dans la plupart des cas, ou des solutions qui s'approchent du meilleur résultat.

Promenade heuristique

On peut obtenir de bons résultats approchés tout simplement en orientant la recherche en profondeur. L'orientation consiste à choisir l'ordre dans lequel on parcourt les arêtes d'un noeud donné. L'idée est de choisir en premier celles qui éloignent de la sortie (selon une distance à préciser : euclidienne, Max, Manhattan...).

Si vous avez stocké le graphe sous forme de tableau de listes d'adjacence (comme pour le précédent TP), vous pouvez trier ces listes en fonction du point de sortie avant de faire le parcours en profondeur classique. Sinon, vous pouvez modifier l'ordre de parcours des arêtes selon la position du noeud courant par rapport à la sortie.

Écrire cette promenade et comparez avec les résultats obtenus avec la méthode précédente. Vous pouvez encore améliorer l'algorithme de la même façon que précédemment, en vérifiant avant de faire la recherche s'il existe un chemin vers la sortie. Testez la différence avec les très gros labyrinthes.

Faire des détours

Le résultat de l'algorithme précédent donne la solution suivante sur le grand labyrinthe :
Un être humain moyennement constitué voit facilement comment améliorer ce chemin. Par exemple, on peut dès le début faire un petit détour qui rajoute 9 cases.

Écrire un algorithme qui, partant d'un chemin (par exemple celui obtenu avec l'heuristique précédente), cherche à l'allonger en y rajoutant des détours. Faire cela de façon récursive jusqu'à ce qu'on ne puisse plus rajouter de détour.

Solution