Come si confrontano l'algoritmo di Dijkstra e A-Star?


154

Stavo guardando cosa hanno fatto i ragazzi del Concorso Mario AI e alcuni di loro hanno costruito dei robot Mario piuttosto carini usando l'algoritmo A * (A-Star) Pathing.

testo alternativo
( Video di Mario A * Bot In Action )

La mia domanda è: come si confronta A-Star con Dijkstra? Guardandoli, sembrano simili.

Perché qualcuno dovrebbe usarne uno sopra l'altro? Soprattutto nel contesto del pathing nei giochi?



@SLaks A * utilizza più memoria di dijkstra? Come mai se un * solo percorso promette nodi mentre dijkstra li prova tutti?
Poutrathor,

Risposte:


177

Dijkstra è un caso speciale per A * (quando l'euristica è zero).


1
In Dijkstra, consideriamo solo la distanza dalla fonte, giusto? E il vertice minimo è preso in considerazione?
Kraken,

4
Ho pensato che A * fosse un caso speciale per Dijkstra in cui usano un euristico. Dal momento che Dijkstra era lì prima afaik.
Madmenyo,

46
@MennoGouw: Sì, l'algoritmo di Dijkstra è stato sviluppato per primo; ma è un caso speciale dell'algoritmo più generale A *. Non è affatto insolito (in effetti, probabilmente, la norma) che vengano scoperti prima casi speciali e successivamente generalizzati.
Pieter Geerkens,

1
Ottima risposta per chiunque conosca l'euristica;)
lindhe

1
A * e l'uso dell'euristica sono discussi bene nel libro AI di Norvig e Russel
BoltzmannBrain,

113

Dijkstra:

Ha una funzione di costo, che è il valore di costo reale da sorgente a ciascun nodo: f(x)=g(x).
Trova il percorso più breve dalla sorgente ad ogni altro nodo considerando solo il costo reale.

Una ricerca:

Ha due funzioni di costo.

  1. g(x): uguale a Dijkstra. Il costo reale per raggiungere un nodo x.
  2. h(x): costo approssimativo dal nodo xal nodo obiettivo. È una funzione euristica. Questa funzione euristica non dovrebbe mai sopravvalutare il costo. Ciò significa che il costo reale per raggiungere il nodo obiettivo dal nodo xdovrebbe essere maggiore o uguale h(x). Si chiama euristico ammissibile.

Il costo totale di ciascun nodo è calcolato da f(x)=g(x)+h(x)

Una * ricerca espande un nodo solo se sembra promettente. Si concentra solo per raggiungere il nodo obiettivo dal nodo corrente, non per raggiungere tutti gli altri nodi. È ottimale se la funzione euristica è ammissibile.

Quindi, se la tua funzione euristica è buona per approssimare il costo futuro, allora dovrai esplorare molti meno nodi di Dijkstra.


20

Ciò che ha detto il poster precedente, in più perché Dijkstra non ha euristiche e ad ogni passo seleziona bordi con il minor costo tende a "coprire" più del tuo grafico. Per questo motivo Dijkstra potrebbe essere più utile di A *. Un buon esempio è quando hai diversi nodi target candidati, ma non sai quale sia il più vicino (nel caso A * dovresti eseguirlo più volte: una volta per ogni nodo candidato).


17
Se ci sono diversi potenziali nodi obiettivo, si potrebbe semplicemente cambiare la funzione di test obiettivo per includerli tutti. In questo modo, A * dovrebbe essere eseguito una sola volta.
Brad Larsen,

9

L'algoritmo di Dijkstra non verrebbe mai utilizzato per l'individuazione di percorsi. L'uso di A * è un gioco da ragazzi se riesci a trovare un'euristica decente (di solito facile per i giochi, specialmente nei mondi 2D). A seconda dello spazio di ricerca, a volte è preferibile Iterative Deepening A * perché utilizza meno memoria.


5
Perché Dijkstra non dovrebbe mai essere usato per l'individuazione di percorsi? Puoi elaborare?
KingNestor,

2
Perché anche se riesci a inventare un pessimo euristico, farai meglio di Dijkstra. A volte anche se è inammissibile. Dipende dal dominio. Dijkstra inoltre non funzionerà in situazioni di memoria insufficiente, mentre IDA * funzionerà.
Shaggy Frog,


7

Dijkstra è un caso speciale per A *.

Dijkstra trova i costi minimi dal nodo iniziale a tutti gli altri. A * trova il costo minimo dal nodo iniziale al nodo obiettivo.

L'algoritmo di Dijkstra non verrebbe mai utilizzato per la ricerca del percorso. Usando A * si può ottenere un'euristica decente. A seconda dello spazio di ricerca, è preferibile l'iterativo A * perché utilizza meno memoria.

Il codice per l'algoritmo di Dijkstra è:

// A C / C++ program for Dijkstra's single source shortest path algorithm.
// The program is for adjacency matrix representation of the graph

#include <stdio.h>
#include <limits.h>

// Number of vertices in the graph
#define V 9

// A utility function to find the vertex with minimum distance value, from
// the set of vertices not yet included in shortest path tree
int minDistance(int dist[], bool sptSet[])
{
 // Initialize min value
 int min = INT_MAX, min_index;

  for (int v = 0; v < V; v++)
   if (sptSet[v] == false && dist[v] <= min)
     min = dist[v], min_index = v;

   return min_index;
}

 int printSolution(int dist[], int n)
 {
  printf("Vertex   Distance from Source\n");
  for (int i = 0; i < V; i++)
     printf("%d \t\t %d\n", i, dist[i]);
  }

void dijkstra(int graph[V][V], int src)
{
 int dist[V];     // The output array.  dist[i] will hold the shortest
                  // distance from src to i

 bool sptSet[V]; // sptSet[i] will true if vertex i is included in shortest
                 // path tree or shortest distance from src to i is finalized

 // Initialize all distances as INFINITE and stpSet[] as false
 for (int i = 0; i < V; i++)
    dist[i] = INT_MAX, sptSet[i] = false;

 // Distance of source vertex from itself is always 0
 dist[src] = 0;

 // Find shortest path for all vertices
 for (int count = 0; count < V-1; count++)
 {
   // Pick the minimum distance vertex from the set of vertices not
   // yet processed. u is always equal to src in first iteration.
   int u = minDistance(dist, sptSet);

   // Mark the picked vertex as processed
   sptSet[u] = true;

   // Update dist value of the adjacent vertices of the picked vertex.
   for (int v = 0; v < V; v++)

     // Update dist[v] only if is not in sptSet, there is an edge from 
     // u to v, and total weight of path from src to  v through u is 
     // smaller than current value of dist[v]
     if (!sptSet[v] && graph[u][v] && dist[u] != INT_MAX 
                                   && dist[u]+graph[u][v] < dist[v])
        dist[v] = dist[u] + graph[u][v];
 }

 // print the constructed distance array
 printSolution(dist, V);
 }

// driver program to test above function
int main()
 {
 /* Let us create the example graph discussed above */
 int graph[V][V] = {{0, 4, 0, 0, 0, 0, 0, 8, 0},
                  {4, 0, 8, 0, 0, 0, 0, 11, 0},
                  {0, 8, 0, 7, 0, 4, 0, 0, 2},
                  {0, 0, 7, 0, 9, 14, 0, 0, 0},
                  {0, 0, 0, 9, 0, 10, 0, 0, 0},
                  {0, 0, 4, 14, 10, 0, 2, 0, 0},
                  {0, 0, 0, 0, 0, 2, 0, 1, 6},
                  {8, 11, 0, 0, 0, 0, 1, 0, 7},
                  {0, 0, 2, 0, 0, 0, 6, 7, 0}
                 };

dijkstra(graph, 0);

return 0;
}

Il codice per l'algoritmo A * è:

class Node:
def __init__(self,value,point):
    self.value = value
    self.point = point
    self.parent = None
    self.H = 0
    self.G = 0
def move_cost(self,other):
    return 0 if self.value == '.' else 1

def children(point,grid):
x,y = point.point
links = [grid[d[0]][d[1]] for d in [(x-1, y),(x,y - 1),(x,y + 1),(x+1,y)]]
return [link for link in links if link.value != '%']
def manhattan(point,point2):
return abs(point.point[0] - point2.point[0]) + abs(point.point[1]-point2.point[0])
def aStar(start, goal, grid):
#The open and closed sets
openset = set()
closedset = set()
#Current point is the starting point
current = start
#Add the starting point to the open set
openset.add(current)
#While the open set is not empty
while openset:
    #Find the item in the open set with the lowest G + H score
    current = min(openset, key=lambda o:o.G + o.H)
    #If it is the item we want, retrace the path and return it
    if current == goal:
        path = []
        while current.parent:
            path.append(current)
            current = current.parent
        path.append(current)
        return path[::-1]
    #Remove the item from the open set
    openset.remove(current)
    #Add it to the closed set
    closedset.add(current)
    #Loop through the node's children/siblings
    for node in children(current,grid):
        #If it is already in the closed set, skip it
        if node in closedset:
            continue
        #Otherwise if it is already in the open set
        if node in openset:
            #Check if we beat the G score 
            new_g = current.G + current.move_cost(node)
            if node.G > new_g:
                #If so, update the node to have a new parent
                node.G = new_g
                node.parent = current
        else:
            #If it isn't in the open set, calculate the G and H score for the node
            node.G = current.G + current.move_cost(node)
            node.H = manhattan(node, goal)
            #Set the parent to our current item
            node.parent = current
            #Add it to the set
            openset.add(node)
    #Throw an exception if there is no path
    raise ValueError('No Path Found')
def next_move(pacman,food,grid):
#Convert all the points to instances of Node
for x in xrange(len(grid)):
    for y in xrange(len(grid[x])):
        grid[x][y] = Node(grid[x][y],(x,y))
#Get the path
path = aStar(grid[pacman[0]][pacman[1]],grid[food[0]][food[1]],grid)
#Output the path
print len(path) - 1
for node in path:
    x, y = node.point
    print x, y
pacman_x, pacman_y = [ int(i) for i in raw_input().strip().split() ]
food_x, food_y = [ int(i) for i in raw_input().strip().split() ]
x,y = [ int(i) for i in raw_input().strip().split() ]

grid = []
for i in xrange(0, x):
grid.append(list(raw_input().strip()))

next_move((pacman_x, pacman_y),(food_x, food_y), grid)

saltare i vicini che sono già in serie chiusa darà un valore non ottimale. Provandolo su questo grafico (è un esempio di video di YouTube, ignora la lingua) darà una risposta sbagliata.
itsjwala,

5

Dijkstra trova i costi minimi dal nodo iniziale a tutti gli altri. A * trova il costo minimo dal nodo iniziale al nodo obiettivo.

Pertanto sembrerebbe che Dijkstra sarebbe meno efficiente quando tutto ciò di cui hai bisogno è la distanza minima da un nodo all'altro.


2
Questo non è vero. Dijkstra standard viene utilizzato per fornire il percorso più breve tra due punti.
Emil,

3
Per favore, non indurre in errore, Dijkstra dà il risultato da s a tutti gli altri vertici. Quindi funziona più lentamente.
Ivan Voroshilin,

Io secondo commento @Emil. Tutto quello che devi fare è fermarti quando rimuovi il nodo di destinazione dalla coda prioritaria e hai il percorso più breve dall'origine alla destinazione. Questo era l'algoritmo originale in realtà.
seteropere,

Più precisamente: se viene specificato un target, Dijkstra trova il percorso più breve per tutti i nodi che si trovano su percorsi più corti del percorso verso il target specificato. Lo scopo dell'euristico in A * è quello di potare alcuni di questi percorsi. L'efficacia dell'euristica determina il numero di potature.
Waylon Flinn,

@seteropere, ma cosa succede se il nodo di destinazione è l'ultimo nodo cercato? È sicuramente meno efficiente, dal momento che l'euristica di A * e la scelta di nodi prioritari sono ciò che aiuta a garantire che il nodo di destinazione cercato non sia l'ultimo nodo dell'elenco
Knight0fDragon,

5

Puoi considerare A * una versione guidata di Dijkstra. Il significato, invece di esplorare tutti i nodi, verrà utilizzato un euristico per scegliere una direzione.

Per dirla più concretamente, se stai implementando gli algoritmi con una coda di priorità, la priorità del nodo che stai visitando sarà una funzione del costo (costo dei nodi precedenti + costo per arrivare qui) e la stima euristica da qui all'obiettivo. Mentre in Dijkstra, la priorità è influenzata solo dal costo effettivo per i nodi. In entrambi i casi, il criterio di arresto sta raggiungendo l'obiettivo.


2

L'algoritmo di Dijkstra trova sicuramente il percorso più breve. D'altra parte A * dipende dall'euristica. Per questo motivo A * è più veloce dell'algoritmo di Dijkstra e darà buoni risultati se hai una buona euristica.


4
A * fornisce gli stessi risultati di Dijkstra, ma più veloce quando si utilizza una buona euristica. Un algoritmo * impone alcune condizioni per funzionare correttamente, ad esempio la distanza stimata tra il nodo corrente e il nodo finale dovrebbe essere inferiore alla distanza reale.
Alexandru,

4
A * è garantito per fornire il percorso più breve quando l'euristica è ammissibile (sottovaluta sempre)
Robert

1

Se guardi il codice psuedoc per Astar:

foreach y in neighbor_nodes(x)
             if y in closedset
                 continue

Considerando che, se guardi lo stesso per Dijkstra :

for each neighbor v of u:         
             alt := dist[u] + dist_between(u, v) ;

Quindi, il punto è che Astar non valuterà un nodo più di una volta,
poiché ritiene che guardare un nodo una volta sia sufficiente, a causa
della sua euristica.

OTOH, l'algoritmo di Dijkstra non è timido nel correggersi, nel caso in cui si
ritorni di nuovo un nodo.

Ciò dovrebbe rendere Astar più veloce e più adatto alla ricerca di percorsi.


7
Questo non è vero: A * può guardare i nodi più di una volta. Di fatto, Dijkstra è un caso speciale di A * ...
Emil,

2
Controllare questo chiarimenti: stackoverflow.com/questions/21441662/...
spiralmoon

Tutti gli algoritmi di ricerca hanno una "frontiera" e un "insieme visitato". Nessuno dei due algoritmi corregge il percorso verso un nodo una volta che si trova nell'insieme visitato: per impostazione predefinita, spostano i nodi dalla frontiera all'insieme visitato in ordine di priorità. Le distanze minime note ai nodi possono essere aggiornate solo mentre si trovano sulla frontiera. Dijkstra è una forma di ricerca al primo posto e un nodo non verrà mai rivisitato una volta inserito nel set "visitato". A * condivide questa proprietà e utilizza uno stimatore ausiliario per scegliere quali nodi sulla frontiera dare la priorità. en.wikipedia.org/wiki/Dijkstra%27s_algorithm
pygosceles

0

In A *, per ciascun nodo si controllano le connessioni in uscita per il loro.
Per ogni nuovo nodo si calcola il costo più basso finora (csf) in base ai pesi delle connessioni a questo nodo e ai costi che dovevano raggiungere il nodo precedente.
Inoltre, si stima il costo dal nuovo nodo al nodo di destinazione e lo si aggiunge al CSF. Ora hai il costo totale stimato (ecc.). (etc = csf + distanza stimata rispetto al target) Successivamente scegli tra i nuovi nodi quello con il più basso ecc.
Fai lo stesso di prima fino a quando uno dei nuovi nodi sarà il target.

Dijkstra funziona quasi allo stesso modo. Tranne che la distanza stimata rispetto al target è sempre 0 e l'algoritmo si interrompe prima quando il target non è solo uno dei nuovi nodi , ma anche quello con il csf più basso.

A * è di solito più veloce di dijstra, anche se non sarà sempre così. Nei videogiochi spesso prefari l'approccio "abbastanza vicino per un gioco". Pertanto, il percorso ottimale "abbastanza vicino" da A * di solito è sufficiente.


-1

L'algoritmo di Dijkstra è sicuramente completo e ottimale per trovare sempre il percorso più breve. Tuttavia, tende a impiegare più tempo poiché viene utilizzato principalmente per rilevare più nodi obiettivo.

A* searchd'altra parte conta con i valori euristici, che puoi definire per raggiungere il tuo obiettivo più vicino, come la distanza di manhattan verso l'obiettivo. Può essere ottimale o completo, a seconda dei fattori euristici. è decisamente più veloce se hai un singolo nodo obiettivo.

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.