Recherche du plus long chemin

Résolu/Fermé
speedzealot Messages postés 18 Date d'inscription vendredi 24 juillet 2015 Statut Membre Dernière intervention 17 novembre 2015 - 27 août 2015 à 17:52
speedzealot Messages postés 18 Date d'inscription vendredi 24 juillet 2015 Statut Membre Dernière intervention 17 novembre 2015 - 28 août 2015 à 14:11
Bonjour a tous !

Donc j'ai cet arbre (je suis pas sure que ce soit un arbre, mais appelons ca un arbre tout de même vue que son fonctionnement reste le même)
on dira que cet arbre a une dimension 10*10, 10 en largeur, 10 en hauteur (pour symplifier):


et je souhaite récuperer 100 DES plus LONG chemins (je n'ai pas besoins d'avoir les 100 plus longs, un chemin de longueur Dim/2 me suffirai) pour joindre 01 à 99 en suivant quelques règles :
- Pas de retour en arrière
- Pas de boucle
- Pas de blocs. Exemple de chemin incorrecte : 01 -> 02 ->12 -> 11

Donc je suis a la recherche d'un algo performant qui pourrai réaliser cette tache relativement rapidement

J'ai déja essayé plusieurs algo classiques (parcours en largeur et en profondeur) et Dijkstra mais c'est trop long puisque ces algo parcourent tout l'arbre et il y a a peu près 54 000 possibilitées pour joindre 01 à 99 en suivant ces règles, et environ 230 000 pour un arbre de dimension 11*10. Je vous laisse immaginer le temps que mettra ces algo pour parcourir un arbre de dimension 18*15 (mon objectif)...
Et je ne vois pas comment A* pourrai m'aider pour cette tache.

Pour l'incetant, le plus rapide que j'ai c'est un parcours en profondeur : si le parcours atteinds le noeud 99 avec une distance de parcours supérieur a Dim/1.8 alors on le conserve, et on continue jusqu'a en obtenir 100. Le problème c'est la diversité : avec cette methode jobtiens 100 chemins, mais 90 qui se ressemplent vraiment (à 2 noeuds près c'est les mêmes). Donc je suis obligé d'en générer 1000 et de supprimer ceux qui se ressemblent trop et ca prends une plombe...

Si vous avez des idées, elles seront le bienvenue !

(je ne sais pas si j'ai placé ce post au bon endroit : je travail en java pour ce projet mais c'est la logique que je veux, le code je le ferai moi-même si j'ai la logique ^^)
A voir également:

1 réponse

KX Messages postés 16733 Date d'inscription samedi 31 mai 2008 Statut Modérateur Dernière intervention 31 janvier 2024 3 015
Modifié par KX le 27/08/2015 à 19:11
Bonjour,

"je suis pas sure que ce soit un arbre, mais appelons ca un arbre tout de même vue que son fonctionnement reste le même"
Pour bien commencer, non ce n'est pas un arbre, et non le fonctionnement n'est pas le même. Avec un arbre tu ne peux pas passer d'un noeud "grand-parent" à un noeud "fils" sans passer par le noeud "parent".

Exemple :

Ça c'est un arbre, tu ne peux pas partir de A et aller à C (ou D) sans passer par B.
    A
/
B
/ \
C D

Ça ce n'est pas un arbre, tu peux partir de A et aller à D en passant par B ou E. Ce qui induis donc que D a "deux parents" (B et E) ce qui est contraire à la définition d'un arbre...
    A
/ \
B E
/ \ /
C D

L'avantage d'un arbre c'est qu'il n'y a pas de cycle, ce qui va clairement te poser problème dans ton algorithme ou tu vas tourner en rond...

Ici ton graphe est ce que l'on appelle une grille, d'ailleurs sa représentation aurait pu être simplifiée avec des lignes horizontales et verticales :
01---02---03---
| | |
11---12---13---
| | |
21---22---23---
| | |

Notons au passage que pour relier 90 cases, théoriquement tu ne peux pas avoir plus de 89 arêtes, sinon tu auras forcément des retours en arrière ou des boucles (PS. Je n'ai pas compris ce qu'était un "bloc").

Voici pour info une des solutions optimales de ton problème :
01   02---03   04---05   06---07   08---09
| | | | | | | | |
11---12 13---14 15---16 17---18 19
|
21---22 23---24 25---26 27---28 29
| | | | | | | | |
31 32---33 34---35 36---37 38---39

Cette forme "en serpent" est caractéristique, pour maximiser le chemin il faut forcément tourner et tourner encore autour des même noeuds. Donc il est normal que tu n'ais pas une grande diversité dans tes résultats, car plus la solution sera bonne plus elle sera proche d'un serpent et si tu veux une autre forme, ce sera nécessairement au prix d'un chemin moins long.

L'algorithme A* (puisque tu en parles) se base sur une heuristique. Si tu codes ton heuristique de manière à serpenter autour de tes noeuds tu devrais arriver à un chemin relativement long.

Je pose un algo dans ce sens (en reprenant mon exemple plus haut), mais évidement je n'ai aucune idée de ce que ça peut donner en pratique :

Soit X1,Y1 le noeud d'où je viens et X2,Y2 le noeud où je suis (c'est à dire que je viens de tracer l'arête X1Y1-X2Y2) et X3,Y3 le noeud où je veux aller (c'est à dire que l'on va tracer X2Y2-X3Y3 à la prochaine étape)
 
  • Si X2>X1 (on a fait gauche-droite), alors X3=X2, Y3=Y2-1 (bas-haut)
  • Si X2<X1 (on a fait droite-gauche), alors X3=X2, Y3=Y2+1 (haut-bas)
  • Si Y2>Y1 (on a fait haut-bas), alors X3=X2+1, Y3=Y2 (gauche-droite)
  • Si Y2<Y1 (on a fait bas-haut), alors X3=X2-1, Y3=Y2 (droite-gauche)

Remarque : avec une heuristique pareille, même pas besoin de A*, un algorithme glouton pourrait très bien faire l'affaire (ce qui est encore plus rapide à mettre en place et à exécuter).

Par contre il faudra voir comment gérer les cas où la case cible existe déjà, soit en explicitant un second choix pour chaque cas de figure, soit en faisant un peu faire le hasard (ce qui augmentera ta diversité).La confiance n'exclut pas le contrôle
1
speedzealot Messages postés 18 Date d'inscription vendredi 24 juillet 2015 Statut Membre Dernière intervention 17 novembre 2015 3
27 août 2015 à 20:56
Bonjour KX, merci de ta réponse,

et merci pour ton explication (je ne connaissait pas le terme de "grille").

Pour le bloc, on va faire un exemple se basant sur l'image que j'ai (c'est en diagonale parce que Mind Mup ne me permet pas de créer des angles droits et je trouve ca plus clair qu'un affichage texte)

Donc, pour ce qui est de ces blocs, je veux dire que le chemin devra etre séparé d'un noeud de toute autre partie de ce même chemin

donc quand je fais : 01 -> 02 -> 12 -> 11, le 11 est adjacent a 01, ce que je ne souhaite pas
un chemin correcte selon cette règle serai plutot 01 -> 02 ->12 -> 22 -> 21. ou la aucun noeud est adjacent a tout autre noeud du chemin (a l'exception du parent évidemment).
(en fait les Noeuds qui m'interessent sont les noeuds qui ne sont pas sur le chemin, et je ne veux pas en avoir trop d'où la recherche des longs chemins)

Après, la solution du serpentin, le gros défaut c'est la diversité, et cela ne donnera pas forcément un long chemin si on déplace le noeud final : si, pour le même graphe on déplace la sortie au noeud 56 c'est mort...

(c'etait la solution que je faisait avant (sur un tableau, pas besoins de graphe pour faire ca), j'y avait rajouté des paramètres aléatoire par rapport au changement de direction, mais j'ai tout effacé pour trouvé un truc plus optimal)

Ca voudrai dire que l'heuristique d'A* serai en fonction de la taille du graphe (oui, le serpentin ne marche pas bien pour des dimensions impaires), et en fonction de l'emplacement de la sorte en fonction de sa position relative par rapport a la forme de la grille, enfin le tout donnerai une trop grosse surcharge de calculs.. (après j'ai peut-etre tord, je n'en sait rien ^^)

ah et non, l'absense de diversité viens du fait que je fais un parcours en profondeur, pas de la forme serpentin (oui, il y a Beaucoup de manière pour obtenir un chemin de taille Dim/2, et les solutions optimales il y en a une petite dizaine (j'ai pas vraiment compté, c'est chiant de se ballader dans un fichier txt de 54000 ligne :D) qui ont a peu près la même taille, donc pas que des serpentins ^^)

Pour parcours en profondeur, admettons qu'il ai trouvé un chemin de taille 50 en terminant par 97 -> 98 -> 99, la récursion va remonter jusqu'a 97 (a cause de la règle du bloc), puis le prochain sur la liste terminera par 97 -> 87 -> 88 -> 89 -> 99, donc tout le chemin parcouru jusqu'a 97 sera identique pour ces deux chemins trouvés
0
KX Messages postés 16733 Date d'inscription samedi 31 mai 2008 Statut Modérateur Dernière intervention 31 janvier 2024 3 015
27 août 2015 à 23:23
Qu'est-ce qui t'intéresse finalement (histoire de ne pas se fourvoyer) ?

Si j'ai bien compris, tu veux :
  • le moins de "murs" possibles (noeuds qui ne sont pas sur le chemin)
  • plusieurs solutions, de préférences variées
  • bonnes mais pas forcément optimales
  • rapides à obtenir
  • que ça puisse se généraliser (dimensions, point de départ/arrivée...)

Déjà à mon avis il faut oublier A* ton problème ne s'y prête pas (recherche de maximum et pas du minimum, nombre de contraintes importantes, etc.), il va plutôt falloir sortir l'artillerie lourde et s'orienter vers les métaheuristiques.
0
speedzealot Messages postés 18 Date d'inscription vendredi 24 juillet 2015 Statut Membre Dernière intervention 17 novembre 2015 3
Modifié par speedzealot le 28/08/2015 à 09:55
Exactement :) Donc par exemple, un chemin sympatique que j'aimerai optenir pour cette configuration ce serai ca : (il a une longueur de 60, donc Dim/1.8)

(j'aurai peut-etre du commencer par l'image pour etre plus clair, dsl)



l'entrée est en haut a gauche, la sortie en bas a droite, les 0 représentent le chemin généré par l'algo, les autres chiffres sont les noeuds qui m'interesse et je ne veux pas en avoir beaucoup. (donc Dim*0.8/1.8 "murs" me vas tres bien)

au niveau de la diversitée, si un chemin (de 0) ressemble à plus de 66% (ce chiffre n'est pas définitif) à un autre chemin, il sera effacé.


Merci pour m'avoir orienté vers les métaheuristiques (je ne connaissait pas) mais ca a l'aire d'etre exactement ce que je recherche :)

je vais faire des recherches la dessus
0
speedzealot Messages postés 18 Date d'inscription vendredi 24 juillet 2015 Statut Membre Dernière intervention 17 novembre 2015 3
28 août 2015 à 11:56
(je ne peux plus editer mon message, mais 60 c'est pas vraiment Dim/1.8 (= 56) mais c'est une des solutions que mon algo a trouvé ayant une longueur supérieur a Dim/1.8)
0
KX Messages postés 16733 Date d'inscription samedi 31 mai 2008 Statut Modérateur Dernière intervention 31 janvier 2024 3 015
Modifié par KX le 28/08/2015 à 13:56
Je pense que le recuit simulé pourrait obtenir de bons résultats dans ton cas, il ne demande pas trop de code (un peu quand même) et le mécanisme est relativement simple à comprendre. Il y a juste le paramétrage qui est un peu compliqué mais tu as déjà fait ce travail (avec Dim/1.8 & co)

Pseudo-code :

public static Solution recherche(Grille grille) {

Solution solution = new Solution(grille); // une solution quelconque
double energie = solution.energie(); // par exemple le nombre de "murs" dans la solution

// configuration (par exemple)
int limite = 10000; // nombre d'étapes max
double energieObjectif = (double) grille.dimension()/1.8; // on s'arrête si on est en dessous
double temperature = 1; // T(n+1) = T(n) * lambda, T(0) = 1
double lambda = 0.99; // lambda = 0.99 → T(n) = 0.99^n

for (int i=0; i<limite && energie > energieObjectif; i++) {
    Solution proposition = solution.voisin(); // un voisin aléatoire de la solution
    double energie2 = proposition.energie();
    temperature = temperature*lambda; // on peut choisir de faire autrement

    if (energie2 < energie // si la solution est meilleure on la garde
        // sinon on s'autorise à prendre parfois des solutions moins bonnes
        || Math.random() < Math.exp((energie - energie2)/temperature)
    {
        solution = proposition;
        energie = energie2;
    }
}

return solution;
}

Remarque : ça c'est l'algorithme générique du recuit, maintenant il faut l'adapter à ton problème, c'est à dire :

1) Déterminer la solution de départ,
solution = new Solution(grille);
. Elle peut être quelconque (exemple: le chemin le plus court entre l'entrée et la sortie), mais si elle n'est pas trop mauvaise dès le départ (obtenu par une heuristique) ça donnera de meilleurs résultats plus rapidement... Sauf que du coup tu orienteras la recherche et donc tu vas perdre en diversité.
À voir ce que tu veux le plus : diversité vs rapidité.

2) Calculer une solution voisine
Solution proposition = solution.voisin();

Quand on parle de solution voisine, ça veut dire que les deux chemins obtenus doivent se ressembler à "un" changement près. Il ne faut pas que les solutions soient trop différentes l'une de l'autre. Remarque : les voisins sont aléatoirement choisis (et il faut que ce soit rapide vu que c'est l'opération que l'on va faire à chaque tour de boucle).

Par exemple, tu peux couper ton chemin en supprimant une cellule (case bleu sur mon illustration) et tu "recolles" les deux morceaux par une "déviation" (orange) la plus courte possible.
Les deux solutions (à gauche et à droite) peuvent donc être considérées comme voisine, à la suppression d'une case (bleue) près.

Attention : une solution doit toujours être valide, on ne doit jamais obtenir de chemin qui serait faux (par exemple avec des blocs), si en calculant un voisin tu te retrouves avec une solution invalide, il faudra trouver un autre voisin valide.

Il est par exemple envisageable qu'en voulant supprimer une case (bleue), tu doives faire le ménage et supprimer plus de cases (violette) pour que ta déviation (orange) soit un chemin acceptable.
Remarque : une solution voisine peut (et doit pouvoir) être moins bonne que l'original, c'est l'algorithme du recuit qui choisira ensuite s'il accepte ou non la régression pour continuer la recherche.

Le choix des voisins est primordial dans les métaheuristiques, je t'invite à tester plusieurs fonctions de création de voisins pour voir s'il n'y en a pas une qui fonctionne mieux que les autres.

NB. il y a également du paramétrage à faire, en particulier sur la température, à voir ce que ça te donne quand tu exécutes le programme.
0