本文译自 Introduction to A*.
对一个物体来说,移动起来很容易,而寻路则相对复杂。
为什么需要寻路算法?请看——
有一个单位,最初位于地图的底部 start,并希望到达顶部 goal。
没有寻路算法的极端情况:
他可以移动的区域(浅红色)中没有任何阻挡,因此他径直前进。直到在弧形障碍物中心,检测到了障碍物,然后他改变方向。最后沿着(红色)路径绕过“U”形障碍物。
拥有寻路算法:
他扫描更大的区域(浅蓝色),发现较短的路径(蓝色),从而避免走进凹形障碍物。
另外,你也可以完善移动算法以解决这个陷阱情况:
一种方法是,避免生成凹陷障碍物。
另一种方法是,将障碍物的凸包标记为障碍物(只有在目的地位于内部时才进入)。
寻路算法的奥义之一,就是不会等到最后一刻才发现障碍。
我们需要在寻路算法和移动算法之间进行权衡。
提前规划通常较慢,但能给出更好的路径;直线移动通常计算地更快,但可能绕路、或是走进死胡同。
如果环境中的障碍物经常移动,那么提前规划就不那么有价值了。
博主建议同时使用——
在起点与目的地距离很远时、或是有大型、移动缓慢的障碍物时,进行路径规划;
而在区域较小、有快速变化和较短路径时可以考虑直接移动。
算法
博主写了一个新的页面,包含了动态演示,可以参见 Introduction to the A* Algorithm.
寻路算法经常被应用在图论中。
而图论中的图,在数学意义上,是一组顶点。其中一部分顶点是连通、可通行的。
将游戏地图平铺开来,可以视为一张图。
每个单位地块都可以看作一个顶点(或者说网格),相邻的顶点之间有连接的边:
假设我们使用的是二维的网格。
如果你之前没有了解过图,可以看这个网页。
稍后,我将讨论如何从游戏世界中构建其他类型的图(未译)。因为大多数AI或者算法研究的寻路算法,都是针对任意图形,而不是基于网格的游戏而设计的。
我们可以通过一些常识来优化寻路算法,比如——
距离方面:当两个东西相距较远时,则需要更长时间才能从起点移动到终点。
方向方面:如果目的地在东边,那么向东走比向西走更容易到达。
对称性方面:大多数时候,(向北走、再向东走),与(向东走、再向北走)是相同的。
Dijkstra算法和BFS
Dijkstra 算法从起点开始,遍历周围的顶点。
它重复检查最近的、尚未检查的顶点,将其添加到“即将进行遍历周围顶点”的顶点集合。
它从起点向外扩展,直到达到目标。
Dijkstra 算法能够保证找到一条最短路径。(注1:只要没有边具有负开销)(注2:“一条最短路径”,因为经常存在多个开销相等的最短路径。)
在下图中,粉红色方块是起点,蓝色方块是目的地,青色区域显示Dijkstra算法扫描的区域。最淡的青色区域距离起点最远,形成了探索的“前沿”:
贪心算法(Greedy Best-First-Search)以类似的方式工作,区别在于它有一些估算(即启发式,顶点距离目的地的距离 )
对于贪心算法来说,下一次进行检查的顶点,不是选择最接近起点的顶点,而是选择最接近目的地的顶点。
贪心算法不保证找到最短的路径。
然而,它比 Dijkstra 算法运行得快很多,因为它用的启发式函数非常快速地引导它朝向目的地。
例如,如果目的地位于起点的南边,则贪心算法将倾向于关注向南的路径。
在下图中,黄色表示具有高启发式值(达到目标的高成本)的节点,黑色表示具有低启发式值的节点(达到目标的低成本)。它表明,与Dijkstra的算法相比,贪心算法可以非常快速地找到路径:
上面这两个例子都说明了最简单的情况 - 当地图没有障碍物时,最短路径确实是一条直线。
让我们考虑上一节中描述的凹陷障碍。
Dijkstra的算法搜索了更多格子(即更耗时),但保证找到最短的路径:
另一方面,贪心的Best-First-Search做的工作较少,但显然,路径不太好:
贪心算法会试图朝着目标径直前进,即使它不是最好的路径。
它只考虑了(从当前顶点到达目的地)的成本 而 忽略了(从起点到当前顶点)的路径成本,所以它会继续前进,即便所使用的路径会变得非常长。
将这两种算法结合起来会不会很奶思?
A* 是在1968年开发的,用于结合启发式方法(如贪心算法)和其他更 oldschool 的方法(如 Dijsktra 算法)。
这有点不一般。
因为启发式算法通常会为您提供一种解决问题的近似方法,而无法保证最优解。
而 A* 建立在启发式的基础之上,虽然启发式本身并不能保证,但 A* 可以保证最短的路径。
A* 算法
A* 在寻路算法中非常受欢迎。因为它非常灵活,可以在各种环境中使用。
像 Dijkstra 算法一样,A* 可以用来找到最短的路径。
像贪心算法一样,A* 可以使用启发式来指导自己。在简单的情况下,它与贪心算法一样快:
在具有凹陷障碍物的示例中,A* 可以找到与 Dijkstra 一样的最佳路径:
A* 成功的秘诀在于它结合了 Dijkstra 算法使用的信息(支持接近起点的顶点)和贪心算法使用的信息(支持接近目标的顶点)。
在讨论 A* 时,我们会提到以下几个值:
g(n) 表示从起点到当前顶点的确切成本,
h(n) 表示从当前顶点到目的地的启发式估计成本。
在上图中,黄色点表示远离目的地的顶点,蓝绿色点表示远离起点的顶点。
A* 在从起点移动到目的地时会平衡两者。
每次通过主循环,它检查具有 g(n) + h(n) 最低的顶点。
在这里,用 f(n) 表示路径总长,f(n) = g(n) + h(n) 。