Ceci est un exemple de ce que je veux faire via du code. Je sais que vous pouvez utiliser la recherche de points de saut pour passer facilement du nœud vert au nœud rouge sans problème, ou même A *. Mais comment calculer cela avec des chaînes.
Dans l'image, vous pouvez voir qu'il ne faut que 8 mouvements pour passer du nœud vert au nœud rouge lors de la prise du chemin bleu. Le chemin bleu déplace instantanément votre position d'un nœud violet au suivant. L'espace au milieu qui coûte 2 mouvements est un point entre deux zones de distorsion que vous devez déplacer pour y accéder.
Il est clairement plus rapide de prendre le chemin bleu, car il suffit de se déplacer à peu près (à peu près) jusqu'au chemin jaune, mais comment faire par programme?
Pour résoudre ce problème, supposons qu'il existe plusieurs "déformations" violettes autour du graphique que vous pouvez utiliser, ET nous savons exactement où chaque point violet se déformera et où elles se trouvent sur le graphique.
Certaines chaînes violettes sont bidirectionnelles, et d'autres non, ce qui signifie que parfois vous ne pouvez entrer dans une chaîne que d'un côté, mais pas revenir en arrière après la déformation.
J'ai réfléchi à la solution et j'ai seulement conclu que je serais en mesure de calculer le problème en vérifiant la distance à chaque point de distorsion (moins les points unidirectionnels), et la différence entre ces points et les points proches d'eux .
Le programme devrait comprendre d'une manière ou d'une autre qu'il est plus avantageux de prendre la deuxième chaîne, au lieu de marcher depuis le premier saut. Ainsi, au lieu de déplacer 6 points, puis de déformer, puis de déplacer les 8 étapes restantes à pied (ce qui est également plus rapide que de ne pas utiliser de chaînes du tout), il faudrait les 6 mouvements, puis les deux mouvements vers la deuxième chaîne.
EDIT: J'ai réalisé que le chemin bleu prendra en fait 12 coups, au lieu de 8, mais la question reste la même.
La plupart des algorithmes de recherche de chemin sont définis en termes de graphiques, pas en termes de grilles. Dans un graphique, une connexion entre deux nœuds autrement distants n'est pas vraiment un problème.
Cependant, vous devez faire attention à votre heuristique. Avec les trous de ver, la distance minimale entre deux nœuds n'est plus la distance euclidienne et la distance ne satisfait pas l'inégalité du triangle. De telles heuristiques sont inadmissibles pour A *. Vous ne pouvez donc pas utiliser facilement A *.
Bien sûr, les algorithmes de recherche de chemin comme Dijkstra qui n'utilisent pas d'heuristique fonctionneront toujours. Cela ressemble plus à une recherche en largeur et sélectionnera vos trous de ver sans effort supplémentaire. Cependant, Dijkstra visitera plus de nœuds que A * avec une bonne heuristique. (Dijkstra est équivalent à A * avec heuristic(x) = 0
.)
Je pense que A * fonctionnera si vous utilisez une heuristique qui traite tous les trous de ver sortants comme un trou de ver directement sur la cible: l'heuristique peut sous-estimer la distance, mais ne doit jamais la surestimer. C'est à dire. l'heuristique serait:
def wormhole_heuristic(x):
return min(euclidean_distance(x, g) for g in [goal, wormholes...])
Pour une heuristique très précise, vous pouvez (récursivement) ajouter la distance entre le point d'extrémité du trou de ver et l'objectif ou le trou de ver suivant. C'est à dire. en tant que pré-calcul, vous pouvez effectuer la recherche de chemin sur le sous-graphique (totalement connecté) contenant tous les trous de ver et l'objectif, où la distance entre deux nœuds est leur distance euclidienne. Cela peut être bénéfique si le nombre de trous de ver est bien inférieur au nombre de cellules accessibles sur votre grille. La nouvelle heuristique serait:
def wormhole_heuristic(x):
direct = euclidean_distance(x, goal)
via_wormhole = min(euclidean_distance(x, w) + wormhole_path_distance(w, goal) for w in wormholes)
return min(direct, via_wormhole)
Comme @Caleth le souligne dans les commentaires, tout cela est très ajustable et nous pouvons améliorer la première heuristique de trou de ver sans faire une recherche de chemin complète à travers le réseau de trou de ver, en ajoutant la distance entre la dernière sortie de trou de ver et l'objectif. Parce que nous ne savons pas quelle sortie de trou de ver sera utilisée en dernier et que nous ne devons pas surestimer, nous devons supposer la sortie la plus proche de l'objectif:
def wormhole_heuristic(x):
direct = euclidean_distance(x, goal)
to_next_wormhole = min(euclidean_distance(x, w) for w in wormholes)
from_last_wormhole = min(euclidean_distance(w.exit, goal) for w in wormholes)
via_wormhole = to_next_wormhole + from_last_wormhole
return min(direct, via_wormhole)
Vous avez un graphique à 6 sommets sur une grille avec des coordonnées:
A ( 0,0)
B ( 4,7)
C ( 7,4)
D (10,4)
E (16,2)
F (16,0)
Vous pouvez générer un graphique complet sur ces sommets et attribuer un coût à chaque arête où le coût est MAX( ABS( x1 - x2 ), ABS( y1 - y2 ) )
pour les arêtes standard et un coût de 0 pour les trous de ver.
Cela vous donnera les coûts (en tant que matrice d'adjacence):
A B C D E F
- -- -- -- -- -- --
A - 7 7 10 16 16
B 7 - 0 6 12 12
C 7 0 - 3 9 9
D 10 6 3 - 0 6
E 16 12 9 0 - 2
F 16 12 9 6 2 -
S'il existe des déformations unidirectionnelles, ne créez que des arêtes dans le graphique (ou la matrice d'adjacence) qui vont dans cette direction mais pas dans le sens opposé.
Vous pouvez ensuite utiliser algorithme de Dijkstra avec file d'attente prioritaire .
Commencez par A
et poussez chaque Edge adjacent sur la file d'attente prioritaire:
Format: (chemin: coût)
queue = [ (A-B : 7), (A-C : 7), (A-D : 10), (A-E : 16), (A-F : 16) ]
Au fur et à mesure que les éléments sont poussés dans la file d'attente - gardez une trace du coût minimum pour chaque sommet et ne poussez les chemins dans la file d'attente que si leur coût est inférieur au coût minimum existant.
min-costs = { A: 0, B: 7, C: 7, D: 10, E: 16, F: 16 }
Supprimez le premier élément de la file d'attente et, si son coût correspond toujours au coût minimum, repoussez tous les chemins composites formés par ce chemin et ses bords adjacents dans la file d'attente prioritaire (si les chemins composites ont un coût inférieur au minimum existant):
Supprimer: (A-B : 7)
(A-B-A : 14)
- rejetez comme coût plus élevé(A-B-C : 7)
- rejetez au même coût(A-B-D : 13)
- rejetez comme coût plus élevé(A-B-E : 19)
- rejetez comme coût plus élevé(A-B-F : 19)
- rejetez comme coût plus élevéSupprimer (A-C : 7)
(A-C-A : 14)
- rejetez comme coût plus élevé(A-C-B : 7)
- rejetez au même coût(A-C-D : 10)
- rejetez au même coût(A-C-E : 16)
- rejetez au même coût(A-C-F : 16)
- rejetez au même coûtSupprimer (A-D : 10)
(A-D-A : 20)
- rejetez comme coût plus élevé(A-D-B : 16)
- rejetez comme coût plus élevé(A-D-C : 13)
- rejetez comme coût plus élevé(A-D-E : 10)
- insérez dans la file d'attente(A-D-F : 16)
- rejetez au même coûtMaintenant, la file d'attente ressemblera à:
queue = [ (A-D-E : 10), (A-E : 16), (A-F : 16) ]
min-costs = { A: 0, B: 7, C: 7, D: 10, E: 10, F: 16 }
Supprimer (A-D-E : 10)
(A-D-E-A : 26)
- rejetez comme coût plus élevé(A-D-E-B : 22)
- rejetez comme coût plus élevé(A-D-E-C : 19)
- rejetez comme coût plus élevé(A-D-E-D : 10)
- rejetez au même coût(A-D-E-F : 12)
- insérez dans la file d'attenteEnsuite, la file d'attente est:
queue = [ (A-D-E-F : 12), (A-E : 16), (A-F : 16) ]
min-costs = { A: 0, B: 7, C: 7, D: 10, E: 10, F: 12 }
Supprimez (A-D-E-F : 12)
, Constatez que vous êtes arrivé au nœud de destination au coût de 12.
Remarque: les chemins (A-B-C-D-E-F)
, (A-C-D-E-F)
Et (A-D-E-F)
Ont tous le même coût minimum de 12.
Configurez une matrice contenant tous les sommets et utilisez l'algorithme Floyd-Wallenstein ou l'algorithme Bellman-Ford. Les deux résulteront en une matrice avec les chemins les plus courts possibles entre tous les points. Vous pouvez ensuite parcourir la matrice pour trouver le chemin le plus court reliant deux points. (votre problème est le même qu'un TSP asymétrique).