Come funziona A * pathfinding?


67

Vorrei capire a livello fondamentale il modo in cui funziona il * pathfinding. Qualsiasi implementazione di codice o psuedo-codice e visualizzazioni sarebbe utile.


Ecco un piccolo articolo con una GIF animata che mostra l'algoritmo di Dijkstra in movimento.
Ólafur Waage,

Le A * Pages di Amit sono state una buona introduzione per me. Puoi trovare molte buone visualizzazioni alla ricerca di Algoritmo AStar su YouTube.
jdeseno,

Sono stato confuso da una serie di spiegazioni di A * prima di trovare questo fantastico tutorial: policyalmanac.org/games/aStarTutorial.htm Mi riferivo principalmente a quello quando scrissi un'implementazione di A * in ActionScript: newarteest.com/flash /astar.html
jhocking

4
-1 wikipedia trovi articolo su A * con spiegazione, il codice sorgente, visualizzazione e ... . Alcune delle risposte qui hanno collegamenti esterni da quella pagina wiki.
user712092,

4
Inoltre, poiché si tratta di un argomento piuttosto complesso di grande interesse per gli sviluppatori di giochi, penso che desideriamo le informazioni qui. Ricordo che Joel una volta disse che voleva che StackOverflow fosse il primo successo quando la gente cercava domande sulla programmazione di Google.
jhocking

Risposte:


63

Clausola di esclusione della responsabilità

Ci sono tonnellate di esempi di codice e spiegazioni di A * che possono essere trovate online. Questa domanda ha anche ricevuto molte grandi risposte con molti link utili. Nella mia risposta proverò a fornire un esempio illustrato dell'algoritmo, che potrebbe essere più facile da capire rispetto al codice o alle descrizioni.


L'algoritmo di Dijkstra

Per capire A *, ti suggerisco di dare prima un'occhiata all'algoritmo di Dijkstra . Lascia che ti guidi attraverso i passaggi che l'algoritmo di Dijkstra eseguirà per una ricerca.

Il nostro nodo di partenza è Ae vogliamo trovare il percorso più breve verso F. A ciascun bordo del grafico è associato un costo di movimento (indicato da cifre nere accanto ai bordi). Il nostro obiettivo è valutare il costo di viaggio minimo per ciascun vertice (o nodo) del grafico fino a quando non raggiungiamo il nostro nodo obiettivo.

Dijkstra's illustrato, parte 1

Questo è il nostro punto di partenza Abbiamo un elenco di nodi da esaminare, questo elenco attualmente è:

{ A(0) }

Aha un costo di 0, tutti gli altri nodi sono impostati su infinito (in un'implementazione tipica, questo sarebbe qualcosa di simile int.MAX_VALUEo simile).

Dijkstra's illustrato, parte 2

Prendiamo il nodo con il costo più basso dalla nostra lista di nodi (poiché la nostra lista contiene solo A, questo è il nostro candidato) e visitiamo tutti i suoi vicini. Impostiamo il costo di ciascun vicino a:

Cost_of_Edge + Cost_of_previous_Node

e tenere traccia del nodo precedente (mostrato come una piccola lettera rosa sotto il nodo). Apuò essere contrassegnato come risolto (rosso) ora, in modo da non visitarlo più. La nostra lista di candidati ora appare così:

{ B(2), D(3), C(4) }

Dijkstra's illustrato, parte 3

Ancora una volta, prendiamo il nodo con il costo più basso dal nostro elenco ( B) e valutiamo i suoi vicini. Il percorso verso Dè più costoso del costo attuale di D, quindi questo percorso può essere scartato. Everrà aggiunto alla nostra lista di candidati, che ora appare così:

{ D(3), C(4), E(4) }

Dijkstra's illustrato, parte 4

Il prossimo nodo da esaminare è ora D. La connessione a Cpuò essere scartata, poiché il percorso non è più breve del costo esistente. Tuttavia, abbiamo trovato un percorso più breve E, pertanto il costo Ee il nodo precedente verranno aggiornati. La nostra lista ora appare così:

{ E(3), C(4) }

Dijkstra's illustrato, parte 5

Quindi, come abbiamo fatto prima, esaminiamo il nodo con il costo più basso dal nostro elenco, che è ora E. Eha solo un vicino irrisolto, che è anche il nodo di destinazione. Il costo per raggiungere il nodo di destinazione è impostato su 10e il nodo precedente su E. La nostra lista di candidati ora appare così:

{ C(4), F(10) }

Dijkstra's illustrato, parte 6

Successivamente esaminiamo C. Siamo in grado di aggiornare il costo e il nodo precedente per F. Poiché il nostro elenco ora ha Fcome nodo il costo più basso, abbiamo finito. Il nostro percorso può essere costruito facendo il backtracking dei nodi più brevi precedenti.


Un algoritmo *

Quindi potresti chiederti perché ti ho spiegato Dijkstra invece dell'algoritmo A * ? Bene, l'unica differenza è nel modo in cui pesi (o ordina) i tuoi candidati. Con Dijkstra è:

Cost_of_Edge + Cost_of_previous_Node

Con A * è:

Cost_of_Edge + Cost_of_previous_Node + Estimated_Cost_to_reach_Target_from(Node)

Dove Estimated_Cost_to_reach_Target_fromviene comunemente chiamata funzione euristica . Questa è una funzione che proverà a stimare il costo per raggiungere il nodo target. Una buona funzione euristica consentirà di visitare meno nodi per trovare l'obiettivo. Mentre l'algoritmo di Dijkstra si espanderebbe su tutti i lati, A * cercherà (grazie all'euristica) di cercare nella direzione del bersaglio.

La pagina di Eurit sull'euristica ha una buona visione d'insieme dell'euristica comune.


2
Vale la pena notare che l'euristico non spingerà sempre la ricerca a trovare il percorso migliore. Ad esempio, se la tua euristica è la distanza dal bersaglio, ma il percorso praticabile è attorno al bordo della mappa, in questo caso la ricerca cercherà l'intera mappa prima di ottenere il percorso giusto. Sicuramente allora, devi pensare, c'è qualcosa che non capisco? Questo non funziona! - la cosa da capire è che lo scopo di un euristico è quello di ridurre la ricerca nella maggior parte dei casi, e il tuo compito è quello di trovare quello che è il "migliore" di tutte le soluzioni disponibili per le tue esigenze specifiche.
SirYakalot,

2
@AsherEinhorn Sarà ancora meglio (o nel peggiore dei casi uguale) di una ricerca non informata come quella di Djikstra.
Bummzack,

si si, hai assolutamente ragione. Forse non ero chiaro, quell'istanza di cui ho parlato nel commento sopra è un senario teorico del "peggior caso" per A * con quella euristica MA è quello che Dijkstra farebbe OGNI volta. Il più delle volte A * sarà migliore anche con un'euristica molto semplice. Il mio punto era che l'euristico all'inizio può essere fonte di confusione perché la "distanza dal bersaglio" non ha sempre senso per ogni scenario - il punto è che lo fa per la maggior parte.
SirYakalot,


Questa risposta potrebbe usare una menzione di ciò che rende ammissibile un'euristica, nel senso di garantire che A * troverà il percorso più breve. (In breve: per essere ammissibile, l'euristica non deve mai sopravvalutare la distanza effettiva dal bersaglio. L'euristica non ammissibile può talvolta essere utile, ma può far sì che A * restituisca percorsi non ottimali.)
Ilmari Karonen

26

Un * percorso di ricerca è una ricerca di tipo primo in assoluto che utilizza un'euristica aggiuntiva.

La prima cosa che devi fare è dividere l'area di ricerca. Per questa spiegazione la mappa è una griglia quadrata di tessere, perché la maggior parte dei giochi 2D usa una griglia di tessere e perché è semplice da visualizzare. Si noti tuttavia che l'area di ricerca può essere suddivisa nel modo desiderato: una griglia esadecimale forse, o anche forme arbitrarie come Rischio. Le varie posizioni della mappa vengono denominate "nodi" e questo algoritmo funzionerà ogni volta che si ha un gruppo di nodi da attraversare e si hanno connessioni definite tra i nodi.

Ad ogni modo, partendo da una determinata tessera iniziale:

  • Le 8 tessere intorno alla tessera iniziale sono "segnate" in base a a) il costo del passaggio dalla tessera corrente alla tessera successiva (generalmente 1 per movimenti orizzontali o verticali, sqrt (2) per movimento diagonale).

  • A ogni tessera viene quindi assegnato un punteggio "euristico" aggiuntivo - un'approssimazione del valore relativo di spostamento su ogni tessera. Vengono utilizzate diverse euristiche, la più semplice è la distanza in linea retta tra i centri della piastrella data e la piastrella finale.

  • La tessera corrente viene quindi "chiusa" e l'agente si sposta sulla tessera vicina che è aperta, ha il punteggio di movimento più basso e il punteggio euristico più basso.

  • Questo processo viene ripetuto fino al raggiungimento del nodo obiettivo o non ci sono più nodi aperti (il che significa che l'agente è bloccato).

Per i diagrammi che illustrano questi passaggi, fare riferimento a questo buon tutorial per principianti .

Ci sono alcuni miglioramenti che possono essere fatti, principalmente nel migliorare l'euristica:

  • Tenendo conto delle differenze del terreno, della rugosità, della pendenza, ecc.

  • A volte è anche utile fare uno "sweep" attraverso la griglia per bloccare le aree della mappa che non sono percorsi efficienti: una forma a U rivolta verso l'agente, per esempio. Senza un test di spazzata, l'agente prima entrava nella U, si voltava, poi andava e si aggirava attorno al bordo della U. Un agente "reale" nota la trappola a forma di U e semplicemente la evita. Spazzare può aiutare a simulare questo.


1
Una spiegazione con grafico, nodi, spigoli sarebbe più chiara di una semplice tessera. Non aiuta a capire che qualunque sia la struttura dello spazio del tuo gioco, puoi applicare lo stesso algoritmo nella misura in cui hai informazioni sulle posizioni interconnesse in questo spazio.
Klaim,

Direi che in realtà sarebbe meno chiaro, perché è più difficile da visualizzare. Sì, questa spiegazione dovrebbe menzionare che non è richiesta una griglia di tessere; infatti, modificherò quel punto.
scherzando il

14

È tutt'altro che il migliore, ma questa è un'implementazione che ho fatto di A * in C ++ qualche anno fa.

Probabilmente è meglio che ti indichi le risorse piuttosto che tentare di spiegare l'intero algoritmo. Inoltre, mentre leggi l'articolo della wiki, gioca con la demo e vedi se riesci a visualizzare come funziona. Lascia un commento se hai una domanda specifica.

  1. A * su Wikipedia
  2. Una * dimostrazione Java

4
Il tuo esempio Python è in C ++.
Panini di alluminio,

@finish - Bello vedere qualcuno che lo capisce! Le attività quotidiane ruotano attorno a Python in questi giorni. Grazie!
David McGraw,

3
Il tuo esempio C ++ potrebbe anche essere C.
deceleratedcaviar

4
L'esempio potrebbe anche essere in assemblatore per tutta la struttura che ha. Non è nemmeno A *, come è questa la risposta accettata?

4
Mi dispiace che non sia all'altezza, è stato uno dei miei primi tentativi di programmazione quando ho iniziato. Sentiti libero di contribuire con qualcosa ai commenti / modifica il post per condividere la tua soluzione.
David McGraw,

6

L'articolo di ActiveTut su Path Finding potrebbe essere utile. Supera sia l'algoritmo di A * che Dijkstra e le differenze tra loro. È orientato verso gli sviluppatori Flash, ma dovrebbe fornire alcune buone informazioni sulla teoria anche se non si utilizza Flash.


4

Una cosa che è importante visualizzare quando si ha a che fare con A * e l'algoritmo di Dijkstra è che A * è diretto; cerca di trovare il percorso più breve verso un punto particolare "indovinando" quale direzione guardare. L'algoritmo di Dijkstra trova il percorso più breve verso / ogni / punto.


1
Questa non è in realtà una descrizione accurata della differenza tra A * e Dijkstra. È vero che Dijkstra risolve single-source in tutti i punti, ma quando viene utilizzato nei giochi di solito viene interrotto non appena trovi un percorso verso l'obiettivo. La vera differenza tra i due è che A * è informato dall'euristica e può trovare quell'obiettivo con meno rami.

Per aggiungere alla spiegazione di Joe: A * troverà anche il percorso per tutti i punti, se lo permetti, ma nei giochi di solito vogliamo fermarci presto. A * funziona come l'algoritmo di Dijsktra, tranne l'euristica che aiuta a riordinare i nodi per esplorare prima i percorsi più promettenti. In questo modo di solito puoi fermarti anche prima che con l'algoritmo di Dijkstra. Ad esempio, se si desidera trovare un percorso dal centro della mappa verso il lato est, l'algoritmo di Dijkstra esplorerà ugualmente in tutte le direzioni e si fermerà quando troverà il lato est. A * passerà più tempo andando ad est che a ovest, e ci arriverà prima.
tra il

3

Quindi, come prima affermazione, A * è un algoritmo di esplorazione grafica. Di solito nei giochi utilizziamo le piastrelle o altre geometrie del mondo come grafico, ma puoi usare A * per altre cose. I due algoritmi ur per l'attraversamento dei grafici sono approfondimento prima ricerca e ampiezza prima ricerca. In DFS esplori sempre completamente il ramo corrente prima di guardare i fratelli del nodo corrente, e in BFS guardi sempre prima i fratelli e poi i figli. A * cerca di trovare una via di mezzo tra questi in cui esplori giù un ramo (quindi più simile a DFS) quando ti avvicini all'obiettivo desiderato, ma a volte fermati e prova un fratello se potrebbe avere risultati migliori lungo il suo ramo. La matematica attuale è che tieni un elenco di possibili nodi da esplorare in seguito dove ognuno ha una "bontà" punteggio che indica quanto vicino (in una sorta di senso astratto) è all'obiettivo, punteggi più bassi sono migliori (0 significherebbe che hai trovato l'obiettivo). Puoi scegliere quale usare successivamente trovando il minimo del punteggio più il numero di nodi di distanza dalla radice (che è generalmente la configurazione corrente o la posizione corrente nel pathfinding). Ogni volta che esplori un nodo aggiungi tutti i suoi figli a questo elenco e quindi scegli quello nuovo.


3

A livello astratto, A * funziona in questo modo:

  • Trattate il mondo come un numero discreto di nodi connessi, ad es. una griglia o un grafico.
  • Per trovare un percorso attraverso quel mondo, devi trovare un elenco di "nodi" adiacenti all'interno di quello spazio, che porta dall'inizio alla meta.
  • L'approccio ingenuo sarebbe questo: calcolare ogni possibile permutazione dei nodi che iniziano con il nodo iniziale e finiscono nel nodo finale e scelgono il più economico. Ciò richiederebbe ovviamente un'eternità su tutti tranne gli spazi più piccoli.
  • Pertanto approcci alternativi tentano di utilizzare alcune conoscenze sul mondo per indovinare quali permutazioni valgono la pena considerare prima e per sapere se una data soluzione può essere battuta. Questa stima si chiama euristica.
  • A * richiede un'euristica ammissibile . Ciò significa che non sopravvaluta mai.
    • Una buona euristica per problemi di pathfinding è la distanza euclidea perché sappiamo che il percorso più breve tra 2 punti è una linea retta. Ciò non sopravvaluta mai la distanza nelle simulazioni del mondo reale.
  • Un * inizia con il nodo iniziale e prova le successive permutazioni di quel nodo più ciascun vicino, i vicini del suo vicino, ecc., Usando l'euristica per decidere quale permutazione provare dopo.
  • Ad ogni passo, A * osserva il percorso più promettente finora e seleziona il nodo vicino successivo che sembra essere "migliore", in base alla distanza percorsa finora e alla stima euristica di quanto sarebbe rimasto lontano per andare da quello nodo.
  • Poiché l'euristica non sopravvaluta mai e la distanza percorsa finora è nota per essere accurata, sceglierà sempre il passo successivo più ottimistico.
    • Se quel passo successivo raggiunge l'obiettivo, sai che ha trovato il percorso più breve dall'ultima posizione, perché questa era la supposizione più ottimistica di quelli validi rimanenti.
    • Se non ha raggiunto l'obiettivo, viene lasciato come un possibile punto da esplorare in seguito. L'algoritmo ora sceglie la prossima possibilità più promettente, quindi la logica sopra si applica ancora.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.