A*算法入门

一:概述

    A*算法在游戏中应用是十分广泛的,许许多多的游戏在寻路方面都会考虑使用该算法(当然除该算法外,前辈们也想出很多其他办法),它是一种启发式的寻路搜索算法。今天这边重点全面分析探讨A*算法。

 二:术语

    此处对接下来将要讨论的内容中的相关术语约定如下:

    start-node       :起始节点。即:用户请求寻路时的起点

    end-node         :目标节点/称之为结束节点/终点。即:用户请求寻路的终点/目的地点

    current-node    :当前节点。即:当前正在分析的节点

    adjacency-node:邻接节点。即:与当前节点相邻接的节点。如:在 N x M 型地图中,一个节点的邻接节点是指与该相连的周边8个节点,即称为邻接节点。

    evaluate-func   :估值计算函数(即:h-value值计算函数)

    g-value            :从 start-node 节点到 current-node 节点的实际代价

    h-value            :从 current-node 到 end-node 节点的估价值/预估代价

    f-value             :指 current-node 的 g-value + h-value 和值,即:估价值。其定义是:从 start-node 经过 current-node 节点到达 end-node 节点的估价值。(或许可以简单理解为 current-node 的 A* 值吧)

    open-table       :开表。即:待考察节点列表

    close[d]-table   :闭表。即:已考察节点列表(close后面加不加 d,完全不重要,不需要纠结)

三:算法思想

    整体上,A*算法主要是将节点划分为未考察的、待考察的以及已考察的三大类。刚开始时的(所有)节点都是未考察的,而待考察的节点则全都被放在open-table中,所有已经被考察过的节点全都放在close-table中。因此,明显的 open-table、close-table 表起初都是空的。算法细节如下:

    Step_001:重置数据;如:需要重置 start-node 的 g-value = 0;h-value = evaluate-func(........);

    Step_001:将 start-node 入 open-table;

    // 第一层循环逻辑开始

    Step_002:取出 open-table 中 f-value 值最小的节点作为 current-node,并做如下处理。(刚开始时,f-value 最小肯定是 start-node,因为表中只有这个节点)

        Step_003:如果 current-node 不存在,则寻路失败退出

        Step_004:如果 current-node 为 end-node,则寻路成功退出,并将 end-node 的前驱节点置为 current-node,然后返回由 end-node 逆序倒推其前驱节点直到 start-node 的所有节点组成的路径返回

        Step_005:将 current-node 入 close-table;

        // 第二层循环开始

        Step_006:逐个遍历 current-node 的 adjacency-node,并做如下处理

            Step_007:如果 adjacency-node 在闭表中,则不做任何处理

            Step_008:如果 adjacency-node 不在开表中,则更新其 g-value、h-value 值;并将该 adjacency-node 的前驱节点置为 current-node 节点,最后再将 adjacency-node 入 open-table

            Step_009:如果 adjacency-node 已在开表中,则比较如果从当前的 current-node 到该 adjacency-node 的 g-value 是否会比 adjacency-node 此时的 g-value 值更优(即:更小)。

                             如果不会更优,则不做任何处理;

                             如果会更优,则将 adjacency-node 的 g-value 值更新为从当前 current-node 到 adjacency-node 的 g-value 值,并将 adjacency-node 的前驱更改为当前的 current-node节点

            Step_010:回到 Step_006 继续处理下一个邻接节点

        Step_011:只要 open-table 表非空,则继续回到 Step_002 处理

四:讨论

    A*算法思想其实非常容易理解,并且其在寻找单源最短路径方面算是速度最为快速的。与Dijkstra算法相比,因为A*是启发性的搜索算法,其是更有目的性地(依赖启发函数的指引下)向目的节点逼近,它不能说是盲目性地搜索算法。而Dijkstra算法则不同,它是盲目性地按算法规则一步步寻找下去。因此,Dijkstra算法是适合单源到其他所有节点的最短路径的搜索算法。当然,从搜索的结果路径来看,Dijkstra算法从源节点到任何节点的路径肯定都是最短的,而A*却未必,因为A*的启发函数只是预估、估算出代价罢了,没办法保证不把路径给带偏。只是这种路径偏离一般情况下是不会很离谱的,多数情况下也都会非常接近最短路径的,这主要是要看启发函数如何选择,不同的启发函数结果路径可能会不一样。再从时间复杂度上看,Dijkstra算法的时间复杂度,如果是任何两点间的最短路径的话,算法时间复杂度为O(n^3),而A*则要好很多,从上面算法思想中可以大约看出,时间代价最大的是在每一层循环遍历处理所有节点环节上,如果以 N x M 地图来例,且每个节点邻接节点算上斜角的也才8个,记为 m。因此明确两点间的A*寻路时间复杂度 T(n) = n * (c1 + m) + c2,因此,时间复杂度为渐近O(n),而任何两点间的A*寻路时间复杂度为O(n^2)。另外,在第一层循环中,由于Step_003、Step_004步骤的存在,则A*的每次寻路是随时都有可能提前结束的。

    在实际项目中,我们会选择使用A*,但一般还会配合其他的一些技术手段共同协作。因为A*虽说在搜索最短路径方面速度很快,但代价也是很大的。试想现在游戏,随随便便一张地图都有非常多的格子的。如:2D斜45度游戏中游戏地图少则88 x 88,多则大型地图,其格子总数永永不止88 x 88了,因此纯用A*作为寻路的代价就大了(试想,如果在最背的情况下,是需要遍历所有节点的,此时 n = (88 * 88) = 7744次,该值是还没考虑常数系数等成份的)。而且A*对存储空间的要求也比较大,地图格子数越多,需要的存储空间也是越来越大。如上文思想一点中所述,有open-table、有close-table、又有未初化节点表等等。这对一些内存资源本来就比较匮乏的设备来说,也是压力巨大(比如:手持设备等,当然现在的手持设备一般也能承受的住就是了,只要你的app/游戏不乱设计的话,一般都没什么太大压力)。

    今天博文暂且到此,后面再分享有关A*算法的高级话题讨论。

posted @ 2016-07-12 20:40  Jacc.Kim  阅读(4497)  评论(0编辑  收藏  举报