关于A*寻路算法的浅薄研究和实现

引言

        近期总是喜欢挖老本,把以前学习过的或者说走马观花过的一些经典算法拿出来研究研究、实现实现;以复习图算法中的Dijkstra寻路算法为契机,看着该算法扩展节点时不断膨胀的环形路径,好吧,以前似乎用过一个更好一些的算法,它叫做A*(A-star)算法。接下来的内容就来逐步介绍一下由A*算法为切入点所收集的一些研究资料吧,希望对自己未来的游戏开发梦有些许帮助。有所错误还请大家提出指正,更希望真正游戏行业中的开发人员能够提出宝贵的意见。

废话不多说,先放出DEMO:
方格上的A*寻路算法DEMO
其中,横向走一格耗散为1,斜向走一格耗散为1.4,绿色块为可行进区域,黑色块为无法行进区域。右侧可查看寻路时间,选择启发函数。
DEMO利用AS3.0在Flashdevelope环境下编程,为以后方便扩展,大概设计如下:

 

A*算法

一些不正规的术语:

节点:路径中可到达的点
父节点:在特定路径中一个节点的上一个节点
后继节点:一个节点可到达的任意节点
起点:寻路开始的节点
终点:希望到达的节点
open列表:待查节点集合
closed列表:已查节点集合
当前耗散g:在特定路径中从起点到当前节点的权值
估计耗散h:从当前节点到终点的估计权值,由启发函数h求得
路径耗散f:某节点在特定路径中的权值,f=g+h

算法描述:

初始化:起点,终点,open,closed;
添加起点至open,并计算其f值;
while(open非空) {
  选取open中f值最小的节点作为当前节点;
  if(当前节点==终点)
    break;
  for each 当前节点的后继节点{
    计算当前节点的所有后继节点的f值;
    在open和closed上查找该后继节点:
    if(该节点在已open || closed 上且其f值小于当前f值)
      continue;
    else
      替换open || closed中的节点 或 加入open
  }
  将当前节点加入closed
}
以上为A*算法较为普遍的描述,可能不同的地方有所出入。
 

A*算法可能的改进

以在方形网格中实现为例,看看A*算法有什么改进的地方。
以下主要参考自 http://www.cs.auckland.ac.nz/~burkhard/Reports/2003_SS_DanielWichmann2.pdf
和 http://www.policyalmanac.org/games/binaryHeaps.htm
以及《人工智能——一种现代方法》

邻域选择:


由上图可以看到,邻域选择可以有4、8、乃至16邻域,所求得的路径逐步平滑,但是相应的要付出的计算代价就较大。

启发函数的选择:

        传统的启发函数主要有曼哈顿距离、欧式距离和对角距离,具体定义不赘述。DEMO中可以体会具体效果。
        曼哈顿距离,由于计算只涉及加减,所以计算快速;其最大的缺陷就是在非4邻域的情况下过大估计耗散,使得可能无法达到最短路径。但一般的游戏中对寻路的要求往往一个虽然不是最优但是好的路径即可满足要求,所以曼哈顿距离的实用性较高。
        欧式距离,由于计算涉及乘方和开方,所以计算速度不尽如人意,且其过小估计耗散,虽然可以使得在任何邻域情况下得到的路径是最优的,但往往需要遍历更多的节点使得寻路速度进一步下降。在非方格网格的情况下,欧式距离也有较好的适用性(有坐标即有距离)。
        对角线距离,结合了上述两种的特点,使得在4、8邻域下可以得到最优路径,且计算速度较欧式距离有些许提高。

数据结构上的改进:

        可以看到,在寻路过程中,随着open和closed的逐步增大,存储和遍历这两个表的空间和计算时间是巨大的,从open和closed的改进入手。
        堆结构的使用,因为每次循环都要从open列表中选取耗散值最小的一个节点,那么将open列表维护成一个最小堆,可以将该操作的时间复杂度降低。
        open列表限制存储大小,可以同时降低存储空间和计算时间,但付出的代价是完备性的丢失,即不一定可以找到最优解。
        舍弃耗散最大值,在上述结构的基础上,每次需要添加新节点时,舍弃耗散最大的那个节点(即最不可能被扩展的节点)来维持列表大小不变。同样不具备完备性,且具体的数据结构还需要考虑。
        耗散值截断,每次循环中,有资格加入列表的节点拥有满足截断值的耗散值,以减小列表大小,同样不具备完备性。每次循环的截断值可取上一次循环中满足截断值的节点中最小的耗散值。(有点拗口)
        可以看出,大部分在数据结构,或者说寻路策略上的改进,都是以舍弃完备性为代价的,但对于实时性要求较高的游戏来说,这是可以接受的。

多尺度方法:

        对于较大,且相对简单的地图来说,多尺度方法也许是一个选择。(Demo中具有细节的迷宫就不适合)
        将原地图按比例缩小,可以得到尺寸较小但基本具有原本特征的地图,在小尺寸上实施寻路算法可以使得计算速度大大提高。得到的路径点,再映射回原地图。同样,可以预见的是,由于尺寸变化必然舍弃一些细节,该方法必然存在不完备性。
 

地图结构的改进

        主要参考自http://www.ai-blog.net/archives/000152.html
         过去(这个过去可以追溯到仙剑1时代)的大多数游戏,包括现在的一些休闲游戏、回合战棋(SLG)游戏都依然使用方形网格作为基本的地图结构。方形网格具有自动分布的特点,且易实现。然而,对于类似下图(WOW 哈兰)的地形,方形网格则很难拟合出较好的效果,或者说为使得角色行进路线不那么奇怪,单位网格的数量得非常多。


        于是为了初步解决这样的问题,有一种way-points方法,方形网格可以看作是该方法的一种特殊形式,如图所示分布:


        虽然该方法可以较好的拟合复杂的地形,但同样为了不奇怪,分布的节点的数量是巨大的。
        其实可以看到,在平坦地区分布节点往往是不必要的,因为完全可以按直线行走,于是,有了更科学的navigation-mesh(导航格方法),如下图:


        只在边缘必要出铺设节点,地图有多个多边形组合成,可以在多边形内随意行走。这样大大减少了寻路所需要考虑的节点数。
        一切看起来顺理成章且很完美,然而关键在于在导航格上寻路的实现方法是相对复杂的,涉及一些几何的知识。虽然没有了节点的约束(本来必须在节点相连的路径上行走),但是同时也没有了节点提供的明确的前驱后继关系。
 

后记

        在各类游戏中,寻路算法都有着广泛的用途,但寻路的问题并没有完全解决。个人感觉根据实际的需求考虑,包括游戏类型、游戏体验、地图绘制特点可以因地制宜的实现A*寻路算法。本文确有班门弄斧的嫌疑,如有错误还请指出,都是为了共同学习。


待编辑

你可能感兴趣的:(算法,数据结构,游戏,休闲游戏,扩展,存储)