A *寻路如何工作?


67

我想从根本上理解A *寻路的工作方式。任何代码或伪代码实现以及可视化都将有所帮助。


这是一篇带有动画GIF 的小文章,展示了动态的Dijkstra算法。
奥拉维尔Waage

Amit的A * Pages对我来说是一个很好的介绍。在youtube上搜索AStar算法时,您会发现很多很好的可视化效果
jdeseno

我一直困惑的一些A *的说明之前,我发现这个伟大的教程:policyalmanac.org/games/aStarTutorial.htm我主要指的是,当我写在ActionScript A *的实现:newarteest.com/flash /astar.html
2011年

4
-1 维基百科有与解释,源代码,可视化和A *的文章...。这里的一些答案包含来自该Wiki页面的外部链接。
user712092

4
另外,因为这是游戏开发人员非常感兴趣的非常复杂的主题,所以我认为我们希望在此获得信息。我记得乔尔曾经说过,他希望当人们对Google编程提出疑问时,StackOverflow成为热门话题。
2012年

Answers:


63

免责声明

可在网上找到大量的A *代码示例和说明。这个问题也收到了很多很好的答案,并提供了许多有用的链接。在我的回答中,我将尝试提供该算法的一个示例,该示例可能比代码或描述更容易理解。


Dijkstra的算法

为了理解A *,建议您首先看看Dijkstra的算法。让我指导您完成Dijkstra算法将执行搜索的步骤。

我们的起始节点是A,我们想找到的最短路径F。图的每个边缘都有与之相关的移动成本(在边缘旁边以黑色数字表示)。我们的目标是评估图形的每个顶点(或节点)的最小行驶成本,直到我们达到目标节点为止。

Dijkstra的插图,第1部分

这是我们的出发点。我们有一个列表节点要检查,该列表当前为:

{ A(0) }

A的成本为0,所有其他节点都设置为无穷大(在典型的实现中,这将是类似int.MAX_VALUE或相似的)。

Dijkstra的插图,第2部分

我们从节点列表中选择成本最低的节点(因为列表仅包含A,这是我们的候选者)并访问其所有邻居。我们将每个邻居的成本设置为:

Cost_of_Edge + Cost_of_previous_Node

并跟踪上一个节点(在该节点下方显示为粉红色的小字母)。A可以立即标记为已解决(红色),这样我们就不再访问它。现在,我们的候选人列表如下所示:

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

Dijkstra的插图,第3部分

同样,我们从列表(B)中获取成本最低的节点,并评估其邻居。到的路径D比的当前成本昂贵D,因此可以丢弃此路径。E将被添加到我们的候选人列表中,现在看起来像这样:

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

Dijkstra的插图,第4部分

现在要检查的下一个节点D。由于C路径不短于现有成本,因此可以放弃与的连接。我们确实找到了一条更短的路径E,因此E将更新其之前节点的成本。现在,我们的列表如下所示:

{ E(3), C(4) }

Dijkstra的插图,第5部分

因此,就像我们之前所做的那样,我们从清单(现在为)中检查成本最低的节点EE只有一个未解决的邻居,这也是目标节点。到达目标节点的成本设置为10,将其先前节点的成本设置为E。现在,我们的候选人列表如下所示:

{ C(4), F(10) }

Dijkstra的插图,第6部分

接下来我们检查C。我们可以更新的费用和上一个节点F。由于我们的列表现在具有F成本最低的节点,因此我们完成了。我们的路径可以通过回溯先前最短的节点来构建。


A *算法

因此,您可能想知道为什么我向您解释了Dijkstra而不是A *算法?好吧,唯一的区别在于您如何衡量(或排序)候选人。使用Dijkstra,它是:

Cost_of_Edge + Cost_of_previous_Node

使用A *,它是:

Cost_of_Edge + Cost_of_previous_Node + Estimated_Cost_to_reach_Target_from(Node)

其中Estimated_Cost_to_reach_Target_from通常称为启发式函数。此功能将尝试估算到达目标节点的成本。良好的启发式功能将实现,只需访问较少的节点即可找到目标。尽管Dijkstra的算法将扩展到所有方面,但A *将(由于启发式)在目标方向上进行搜索。

阿米特(Amit)关于启发式的页面对常见的启发式有很好的概述。


2
值得注意的是,启发式方法不会总是推动搜索以找到最佳路线。例如,如果您的试探法是距目标的距离,但可行路线在地图边缘附近-在这种情况下,搜索将在搜索到正确路线之前搜索整个地图。那肯定是,您一定在想,有什么我不明白的吗?这行不通!-要了解的是,启发式搜索的目的是在最常见的情况下减少搜索,而您的工作就是在满足特定需求的所有可用解决方案中找到“最佳”解决方案。
SirYakalot

2
@AsherEinhorn它仍然会比Djikstra的不知情的搜索更好(或在最坏的情况下等于)。
bummzack 2012年

是的,您绝对正确。也许我不清楚,我在上面的评论中谈到的实例是A *的理论“最坏情况”,具有启发性的但它是Dijkstra每次都会做的事。即使使用非常简单的启发式方法,大多数情况下A *也会更好。我的观点是,试探法起初可能会造成混乱,因为“到目标的距离”并不总是对每种情况都有意义,而是对大多数情况而言都是如此。
SirYakalot


在保证A *会找到最短路径的意义上,此答案可以提及使启发式成为可接受的内容。(简而言之:试探法必须绝对不要高估到目标的实际距离。有时
不允许的

26

A *路径查找是使用其他启发式的最佳优先类型搜索。

您需要做的第一件事是划分您的搜索区域。对于此说明,地图是一个正方形的方格网格,因为大多数2D游戏都使用一个方格网格,并且因为这很容易可视化。但是请注意,搜索区域可以按照您想要的任何方式进行分解:可能是十六进制网格,甚至是诸如Risk之类的任意形状。各种地图位置称为“节点”,只要您有一堆要遍历的节点并在节点之间定义了连接,该算法便会起作用。

无论如何,从给定的起始图块开始:

  • 根据以下条件对开始的图块周围的8个图块进行“评分”:a)从当前图块移动到下一个图块的成本(水平或垂直移动通常为1,对角线移动通常为sqrt(2))。

  • 然后,为每个图块分配一个附加的“启发式”得分-移至每个图块的相对价值的近似值。使用了不同的启发式方法,最简单的方法是给定图块的中心与端部图块之间的直线距离。

  • 然后,当前“图块”被“关闭”,代理将移动到打开,具有最低移动得分和最低启发式得分的相邻图块。

  • 重复此过程,直到到达目标节点,或者不再有打开的节点(意味着代理已被阻止)。

有关图示这些步骤的图表,请参阅此入门指南

可以进行一些改进,主要是在改进启发式方法方面:

  • 考虑到地形差异,粗糙度,陡度等

  • 在网格上进行“扫掠”以遮挡地图上不是有效路径的区域有时也很有用,例如面向代理的U形。如果没有扫描测试,特工将首先进入U,转身,然后离开并在U的边缘四处走动。“真正的”智能特工会注意到U形陷阱并只是避开了它。扫频可以帮助模拟这一点。


1
关于图,节点,边的解释将比关于图块更清楚。不管您的游戏空间结构如何,只要在该空间中具有相互链接的位置信息,就可以应用相同的算法。
Klaim

我认为这实际上并不太清楚,因为它很难可视化。但是,是的,这种解释应该提到不需要瓦片网格。其实,我在编辑这一点。
jhocking

14

它远非最佳,但是几年前我用C ++实现了A *的实现。

我向您指出资源可能比尝试解释整个算法要好。另外,当您阅读Wiki文章时,请进行演示,看看是否可以直观地看到它的工作方式。如果您有特定问题,请发表评论。

  1. 维基百科上的A *
  2. A * Java演示

4
您的Python示例是C ++。
铝制面包

@finish-很高兴见到别人!如今,日常活动围绕着Python。谢谢!
David McGraw

3
您的C ++示例也可能是
deceleratedcaviar

4
该示例也可能在其具有的所有结构的汇编器中。甚至不是A *,这是公认的答案吗?

4
抱歉,这还没达到标准,这是我刚开始时的第一次编码尝试。随时为评论添加内容/编辑帖子以分享您自己的解决方案。
David McGraw

6

您可能会发现ActiveTut上有关“ 路径查找”的文章很有用。它讨论了A *和Dijkstra的算法,以及它们之间的区别。它面向Flash开发人员,但即使您不使用Flash,它也应提供一些理论上的深刻见解。


4

在处理A *和Dijkstra的算法时,可视化的一件事很重要,那就是A *是定向的。它试图通过“猜测”要寻找的方向来找到到达特定点的最短路径。Dijkstra的算法查找到/ every /点的最短路径。


1
这并不是对A *和Dijkstra之间差异的准确描述。Dijkstra确实可以解决所有问题,但是在游戏中使用时,通常只要找到通往目标的途径,它就会被切断。两者之间的真正区别在于,A *是通过启发式方法获得的,并且可以用较少的分支找到该目标。

补充一下乔的解释:如果允许,A *也会找到所有点的路径,但是在游戏中,我们通常希望尽早停止。A *的工作方式与Dijsktra的算法类似,只是启发式方法有助于重新排序节点以首先探索最有希望的路径。这样,您通常可以比使用Dijkstra的算法更早地停止。例如,如果要查找从地图中心到东侧的路径,Dijkstra的算法将在所有方向上均等地探索,并在找到东侧时停止。A *将花费更多的时间在东方而不是西方上,并更快地到达那里。
2011年

3

因此,就像第一个陈述一样,A *本质上是一种图探索算法。通常在游戏中,我们使用图块或其他世界几何图形作为图形,但您可以将A *用于其他内容。图遍历的两个ur算法是深度优先搜索和宽度优先搜索。在DFS中,在查看当前节点的同级之前,您始终会完全浏览当前分支,而在BFS中,您始终会先查看同级,然后再查看子级。A *试图找到它们之间的中间地带,当您接近期望的目标时,您会探索一个分支(更像DFS),但有时会停下来尝试同级兄弟,如果它在其分支下可能会有更好的结果。实际的数学方法是,您保留可能的节点列表,以探索下一个每个节点都具有“好处”的节点 分数表示(在某种抽象意义上)离目标有多近,分数越低越好(0表示您已找到目标)。通过找到最低分数加上远离根的节点数(通常是当前配置或寻路中的当前位置),可以选择下一步使用哪个。每次浏览一个节点时,都会将其所有子节点添加到此列表中,然后选择新的最佳节点。


3

在抽象级别,A *的工作方式如下:

  • 您将世界视为离散数量的连接节点,例如。网格或图形。
  • 要找到穿越该世界的道路,您需要找到该空间内从起点到目标的相邻“节点”列表。
  • 天真的方法是这样的:计算从起始节点开始到结束节点结束的所有可能的节点排列,然后选择最便宜的。除了最小的空间,这显然将永远占用所有空间。
  • 因此,替代方法尝试使用有关世界的一些知识来猜测哪些排列值得首先考虑,并知道是否可以击败给定的解决方案。此估计称为启发式。
  • A *需要允许的启发式方法。这意味着它永远不会高估。
    • 欧氏距离是对寻路问题的一种很好的启发,因为我们知道两点之间的最短路径是一条直线。这绝不会高估实际模拟中的距离。
  • A *从起始节点开始,并尝试使用该试探法确定下一个尝试的置换,然后再尝试对该节点加上每个邻居及其邻居的邻居进行连续排列。
  • 在每个步骤中,A *都会查看到目前为止最有希望的路径,并根据到目前为止所经过的距离以及启发式的估计距离该距离还有多远,选择似乎是“最佳”的下一个相邻节点。节点。
  • 因为启发式算法永远不会过高估计,并且到目前为止所走的距离是准确的,所以它将始终选择最乐观的下一步。
    • 如果下一个步骤达到了目标,您就会知道它找到了从最后一个位置出发的最短路线,因为这是对剩余有效路线的最乐观猜测。
    • 如果未达到目标,则可以稍后再探讨。该算法现在选择下一个最有前途的可能性,因此上述逻辑仍然适用。
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.