Dijkstra的算法和A-Star相比如何?


154

我一直在研究Mario AI竞赛中的成员正在做的事情,其中​​有些人利用A *(A星)路径算法构建了一些非常简洁的Mario机器人。

替代文字
Mario A * Bot在行动中的视频

我的问题是,A-Star与Dijkstra相比如何?看着它们,它们看起来很相似。

为什么有人会使用另一个?尤其是在游戏中使用路径时?



@SLaks A *比dijkstra使用更多的内存?如果仅在dijkstra尝试所有有希望的节点的同时尝试所有有希望的节点,那怎么办?
Poutrathor

Answers:


177

Dijkstra是A *的特例(启发式为零时)。


1
在dijkstra中,我们只考虑到源的距离,对不对?并且考虑了最小顶点?
Kraken

4
我认为A *对于Dijkstra来说是一种特殊情况,他们使用启发式方法。自从迪克斯特拉(Dijkstra)出现在那之后,他就开始了afaik。
Madmenyo

46
@MennoGouw:是的,首先开发了Dijkstra的算法;但这是更通用的算法A *的特例。首先发现特殊情况,然后对其进行概括,这一点也不罕见(实际上可能是规范)。
Pieter Geerkens 2013年

1
对于任何知道启发式方法的人来说都是一个很好的答案;)
lindhe'5


113

Dijkstra:

它具有一个成本函数,它是从源到每个节点的实际成本值:f(x)=g(x)
它仅考虑实际成本即可找到从源到其他节点的最短路径。

A *搜索:

它具有两个成本函数。

  1. g(x):与Dijkstra相同。到达节点的实际成本x
  2. h(x):从节点x到目标节点的大概成本。这是一种启发式功能。这种启发式功能绝不能高估成本。这意味着,从节点到达目标节点的实际成本x应大于或等于h(x)。这被称为可允许的启发式。

每个节点的总成本由下式计算: f(x)=g(x)+h(x)

A *搜索仅在看起来有希望的情况下才扩展节点。它仅专注于从当前节点到达目标节点,而不是其他所有节点。如果允许启发式函数,则为最佳。

因此,如果您的启发式函数可以很好地估计未来成本,那么与Dijkstra相比,您需要探索的节点要少得多。


20

以前的海报所说的,再加上Dijkstra没有启发式方法,并且每一步都以最小的成本选择边缘,因此倾向于“覆盖”您的图形中的更多部分。因此,Dijkstra可能比A *更有用。一个很好的例子是,当您有多个候选目标节点,但您不知道哪个是最接近的(在A *情况下,您必须多次运行它:每个候选节点一次)。


17
如果存在多个潜在目标节点,则只需更改目标测试功能即可将其全部包含在内。这样,A *只需要运行一次。
布拉德·拉尔森

9

Dijkstra的算法永远不会用于寻路。如果您能想到一个体面的启发式方法(通常对于游戏来说特别容易,尤其是在2D世界中),那么使用A *毫无疑问。根据搜索空间的不同,有时最好使用迭代加深A *,因为它使用较少的内存。


5
为什么Dijkstra永远不会用于寻路?你能详细说明吗?
KingNestor

2
因为即使您可以提出一个糟糕的启发式方法,您也会比Dijkstra做得更好。有时,即使这是不可接受的。它取决于域。Dijkstra也不会在内存不足的情况下工作,而IDA *会工作。
粗野的青蛙


7

Dijkstra是A *的特例。

Dijkstra找到从起始节点到所有其他节点的最低成本。A *查找从起始节点到目标节点的最低成本。

Dijkstra的算法永远不会用于路径查找。使用A *可以得出不错的启发式方法。根据搜索空间,迭代A *是更可取的,因为它使用较少的内存。

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;
}

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)

跳过已经处于封闭状态的邻居将导致次优。在图上尝试(它是一个youtube视频示例,请忽略该语言)将给出错误的答案。
itsjwala

5

Dijkstra找到从起始节点到所有其他节点的最低成本。A *查找从起始节点到目标节点的最低成本。

因此,当您需要的只是从一个节点到另一个节点的最小距离时,Dijkstra似乎效率会降低。


2
这不是真的。标准Dijkstra用于给出两点之间的最短路径。
埃米尔(Emil)

3
请不要误导,Dijkstra会将s的结果提供给所有其他顶点。因此,它的工作速度较慢。
伊万·沃罗林

我第二个@Emil评论。您需要做的就是在从优先级队列中删除目标节点时停止,并且从源到目标的路径最短。这实际上是原始算法。
seteropere

更准确地说:如果指定了目标,则Dijkstra会找到到所有比指定目标的路径短的路径上的所有节点的最短路径。A *中的启发式方法的目的是修剪其中的一些路径。启发式方法的有效性决定了修剪的数量。
Waylon Flinn

@seteropere,但是如果目标节点是搜索到的最后一个节点,该怎么办?它肯定效率较低,因为A *的启发式方法和选择优先级节点有助于确保搜索到的目标节点不是列表中的最后一个节点
Knight0fDragon

5

您可以考虑将A *作为Dijkstra的指导版本。意思是,您将使用启发式方法来选择方向,而不是探索所有节点。

更具体地说,如果您使用优先级队列来实现算法,则您要访问的节点的优先级将是成本(以前的节点成本+到达此处的成本)和从此处得出的启发式估算值的函数达到目标。在Dijkstra中,优先级仅受节点实际成本的影响。无论哪种情况,停止标准都可以达到目标。


2

Dijkstra的算法肯定会找到最短的路径。另一方面,A *取决于启发式方法。因此,A *比Dijkstra的算法快,并且如果您具有良好的启发式算法,则可以给出良好的结果。


4
A *的结果与Dijkstra相同,但是使用良好的启发式方法时,结果更快。A *算法为正常工作施加了一些条件,例如当前节点与最终节点之间的估计距离应小于实际距离。
亚历山德鲁

4
当启发式是可接受的(总是低估)时,保证A *给出最短的路径
罗伯特·罗伯特

1

如果您查看Astar 的伪代码

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

而如果您与Dijkstra相同的话:

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

因此,重点是,Astar不会对节点进行多次评估,
因为它认为一次查看一个节点就足够了,因为
它具有启发性。

OTOH,Dijkstra的算法很容易自行纠正,以防万一
节点再次弹出。

应该使Astar更快,更适合于路​​径查找。


7
这是不正确的:A *可以多次查看节点。实际上,Dijkstra是A *的特例...
Emil


所有搜索算法都有一个“边界”和一个“访问集”。一旦节点进入访问集中,这两种算法都无法校正该节点的路径:根据设计,它们会按照优先级顺序将节点从边界移动到访问集中。到节点的最小已知距离只能在它们位于边界上时进行更新。Dijkstra是最佳优先搜索的一种形式,一旦将节点放入“已访问”集中,就永远不会对其进行重新访问。A *共享此属性,并且它使用辅助估计器来选择边界上的哪些节点优先。en.wikipedia.org/wiki/Dijkstra%27s_algorithm
pygosceles

0

在A *中,对于每个节点,请检查它们的传出连接。
对于每个新节点,您都需要根据与该节点的连接权重以及到达前一个节点所需的成本来计算到目前为止的最低成本(csf)。
另外,您估计从新节点到目标节点的成本,并将其添加到csf中。现在,您有了估算的总成本(等)。(等= csf +到目标的估计距离)接下来,从新节点中选择具有最低
等值的一个。与之前相同,直到新节点之一成为目标。

Dijkstra的工作原理几乎相同。除了估计的到目标的距离始终为0,并且当目标不仅是新节点之一,而且是csf最低的目标时,算法首先停止。

A *通常比dijstra更快,尽管并非总是如此。在视频游戏中,您通常会提倡“足够接近游戏”的方法。因此,从A *开始的“足够接近”的最佳路径通常就足够了。


-1

Dijkstra的算法绝对是完整且最佳的,您将始终找到最短的路径。但是,由于它主要用于检测多个目标节点,因此往往会花费更长的时间。

A* search另一方面,启发式值很重要,您可以定义该值以更接近目标,例如曼哈顿到目标的距离。它可以是最佳的,也可以是完整的,取决于启发式因素。如果您只有一个目标节点,肯定会更快。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.