【问题标题】:How does Dijkstra's Algorithm and A-Star compare?Dijkstra 的算法和 A-Star 的比较如何?
【发布时间】:2010-11-22 21:06:24
【问题描述】:

我正在查看 Mario AI Competition 中的人一直在做什么,其中一些人利用 A*(A-Star)路径算法构建了一些非常简洁的马里奥机器人。


(Video of Mario A* Bot In Action)

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

为什么有人会使用一个而不是另一个?尤其是在游戏中的路径方面?

【问题讨论】:

  • @SLaks A* 使用比 dijkstra 更多的内存?如果一个*只有路径有希望的节点,而 dijkstra 尝试所有这些节点,怎么办?

标签: algorithm artificial-intelligence graph a-star dijkstra


【解决方案1】:

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

【讨论】:

  • 在dijkstra中,我们只考虑到源的距离对吧?并且考虑最小顶点?
  • 我认为 A* 是 Dijkstra 的一个特例,他们使用启发式算法。因为 Dijkstra 是第一个 afaik。
  • @MennoGouw:是的,Dijkstra 的算法是最先开发的;但它是更通用算法 A* 的特例。首先发现特殊情况,然后再推广,这一点也不稀奇(事实上,可能是常态)。
  • A* 和启发式算法的使用在 Norvig and Russel's AI book 中有很好的讨论
  • 这是否意味着 Djikstra 和统一成本搜索是一回事?
【解决方案2】:

迪杰斯特拉:

它有一个成本函数,即从源到每个节点的真实成本值: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 少得多。

【讨论】:

    【解决方案3】:

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

    【讨论】:

    • 如果有多个潜在的目标节点,可以简单地更改目标测试函数以将它们全部包含在内。这样,A* 只需运行一次。
    【解决方案4】:

    Dijkstra 的算法永远不会用于寻路。如果您能想出一个像样的启发式算法(通常对于游戏来说很容易,尤其是在 2D 世界中),那么使用 A* 是一件轻而易举的事情。根据搜索空间,迭代深化 A* 有时更可取,因为它使用的内存更少。

    【讨论】:

    • 为什么 Dijkstra 永远不会用于寻路?你能详细说明一下吗?
    • 因为即使你能想出一个糟糕的启发式方法,你也会比 Dijkstra 做得更好。有时即使它是不可接受的。这取决于域。 Dijkstra 也不会在内存不足的情况下工作,而 IDA* 会。
    【解决方案5】:

    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)
    

    【讨论】:

    • 跳过已经在封闭集中的邻居将给出次优。在 this 图表上尝试(它是一个 youtube 视频示例,忽略语言)会给出错误的答案。
    【解决方案6】:

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

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

    【讨论】:

      【解决方案7】:

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

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

      【讨论】:

      • 这不是真的。标准 Dijkstra 用于给出两点之间的最短路径。
      • 请不要误导,Dijkstra 将 s 的结果提供给所有其他顶点。因此它的工作速度较慢。
      • 我第二个@Emil评论。您需要做的就是在从优先级队列中删除目标节点时停止,并且您拥有从源到目标的最短路径。这实际上是原始算法。
      • 更准确地说:如果指定了目标,Dijkstra 会找到所有节点的最短路径,这些节点位于比指定目标的路径短的路径上。 A* 中启发式的目的是修剪其中一些路径。启发式的有效性决定了修剪的数量。
      • @seteropere,但是如果您的目标节点是搜索的最后一个节点怎么办?它肯定效率较低,因为 A* 的启发式算法和选择优先节点有助于确保搜索的目标节点不是列表中的最后一个节点
      【解决方案8】:

      Dijkstra 算法肯定会找到最短路径。另一方面,A* 取决于启发式。出于这个原因,A* 比 Dijkstra 的算法更快,如果你有一个好的启发式算法,它会给出好的结果。

      【讨论】:

      • A* 给出与 Dijkstra 相同的结果,但当您使用良好的启发式时会更快。 A*算法为正确工作施加了一些条件,例如当前节点与最终节点之间的估计距离应低于实际距离。
      • A* 保证在启发式允许时给出最短路径(总是低估)
      【解决方案9】:

      如果您查看 Astar 的 psuedocode

      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 更快,更适合寻路。

      【讨论】:

      • 这不是真的:A* 可以多次查看节点。事实上,Dijkstra 是 A* 的一个特例……
      • 检查这个以获得澄清:stackoverflow.com/questions/21441662/…
      • 所有搜索算法都有一个“边界”和一个“访问集”。一旦节点位于访问集中,这两种算法都不会更正节点的路径:根据设计,它们按优先级顺序将节点从边界移动到访问集。到节点的最小已知距离只能在它们位于边界时更新。 Dijkstra 是最佳优先搜索的一种形式,一旦将节点放入“已访问”集中,它将永远不会被重新访问。 A* 共享此属性,它使用辅助估计器来选择要优先考虑边界上的哪些节点。 en.wikipedia.org/wiki/Dijkstra%27s_algorithm
      【解决方案10】:

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

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

      A* 通常比 dijstra 快,但情况并非总是如此。 在电子游戏中,您经常喜欢“足够接近游戏”的方法。因此,从 A* 出发的“足够接近”的最优路径通常就足够了。

      【讨论】:

        【解决方案11】:

        BFS 和 Dijkstra 的算法非常相似;它们都是 A* 算法的特例。

        A* 算法不仅更通用;它在某些情况下提高了 Dijkstra 算法的性能,但这并不总是正确的;一般情况下,Dijkstra 算法的速度与 A* 一样快。

        Dijkstra 算法有 3 个参数。如果你曾经解决过网络延迟问题:

        class Solution:
            def networkDelayTime(self, times: List[List[int]], n: int, k: int) -> int:
        

        A* 方法有额外的 2 个参数:

         function aStar(graph, start, isGoal, distance, heuristic)
                queue ← new PriorityQueue()
                queue.insert(start, 0)
        
                # hash table to keep track of the distance of each vertex from vertex "start", 
                #that is, the sum of the weight of edges that needs to be traversed to get from vertex start to each other vertex. 
                # Initializes these distances to infinity for all vertices but vertex start .
                distances[v] ← inf (∀ v ε graph | v <> start)
              
               # hash table for the f-score of a vertex, 
               # capturing the estimated cost to be sustained to reach the goal from start in a path passing through a certain vertex. Initializes these values to infinity for all vertices but vertex "start".
                fScore[v] ← inf (∀ v ε graph | v <> start)
        
                # Finally, creates another hash table to keep track, for each vertex u, of the vertex through which u was reached.
                parents[v] ← null ( v ε graph)
        

        A* 接受两个额外的参数,一个distance function 和一个heuristic。它们都有助于计算所谓的 f 分数。该值是从源到达当前节点 u 的成本和从 u 到达目标所需的预期成本的混合。

        通过控制这两个参数,我们可以获得 BFS 或 Dijkstra 算法(或两者都不是)。对于他们两个,heuristic 需要是一个等于 0 的函数,我们可以这样写

           lambda(v) → 0 
        

        事实上,这两种算法都完全忽略了关于顶点到目标距离的任何概念或信息。

        对于距离指标,情况有所不同:

        • Dijkstra 的算法使用边缘的权重作为距离函数,所以我们需要传递类似的东西

            distance = lambda(e) → e.weight 
          
        • BFS 只考虑遍历的边数,相当于认为所有边的权重相同,等于 1!因此,我们可以通过

            distance = lambda(e) → 1 
          

        A* 仅在我们拥有可以以某种方式使用的额外信息的某些情况下获得优势。当我们知道所有或部分顶点到目标的距离的信息时,我们可以使用 A* 将搜索更快地推向目标。

        请注意,在这种特殊情况下,关键因素是顶点带有额外的信息(它们的位置是固定的),可以帮助估计它们到最终目标的距离。这并不总是正确的,并且通常不是通用图的情况。 换句话说,这里的额外信息不是来自图表,而是来自领域知识。

        The key, here and always, is the quality of the extra information 
        captured by the Heuristic function: the more reliable and closer 
        to real distance the estimate, the better A* performs.
        

        【讨论】:

          【解决方案12】:

          Dijkstra 的算法绝对是完整且最优的,您总能找到最短路径。然而,由于它主要用于检测多个目标节点,因此它往往需要更长的时间。

          A* search 另一方面与启发式值有关,您可以定义它以更接近您的目标,例如曼哈顿到目标的距离。它可以是最优的或完整的,这取决于启发式因素。如果你有一个单一的目标节点,它肯定会更快。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2010-09-10
            • 2011-03-27
            • 1970-01-01
            • 1970-01-01
            • 2011-08-22
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多