A*算法思想容易理解,但要想设计出好的A*算法,则必需要全面深入了解它。在本文章中接下来的内容中,将全面深入探讨该话题。如果对该算法还没有理解的话,则请先查阅上篇文章《A*算法入门》,然后再看该文章。
一:理论篇
探讨:估值函数
A*算法之所以效率高是因为它是启发式的搜索算法。它是在Dijkstra算法的基础上,增加了书籍网路信息的评估,也就是增加了约束条件,从而改变算法的走向 -------- 即:是带有目的性的往目标节点逼近,而不是你Dijkstra算法那样盲目搜索。因此,A*算法的执行效率高低在非常大的程度上是依赖于估值函数的,估值函数构造的越准确,则A*搜索的时间越短。
估值函数的主要任务是预估待搜索节点的重要程度。其定义为从初始节点经过当前节点到达目标节点的最小代价路径的代价估计值。公式:f(n) = g(n) + h(n),f(n)即为估值函数,g(n) 为初始节点到当前节点的已知代价,h(n) 为当前节点到目标节点的预估函数,其实就是所谓的启发函数。其实如果网路点确定的话,h(n)的值也是确定的,关键就是要看h(n)到底是选择什么样的构造函数。不同的h(n),其值肯定不同,从而影响到f(n)的值。
构造的启发函数h(n),不能与实际最短路径相差太远。差的越远,则A*算法的搜索就越接近BFS,特别地,当h(n) = 0时,就完全退化为BFS了。因此搜索的时间就越来越高。因此,如果启发函数能构造成与实际最短路径完全一样的话,则理论上解搜索速度是最快的。但实际中,这是不可能的,因为它只是预估函数。
在实际中,如果想要尽量快速,就要尽量将该函数构造的越接近实际最短路径,而要做到这点,则必需要参考更多的启发信息量。如:当前待考察节点与目标节点的“关系”或系统中的其他一些信息量,比如:经过该当前待考察节点到目标节点的权重等等。但如此一来h(n)的计算量也会随之增加,因此,这是一种权衡的诀择。下面介绍几种觉的启发函数。
探讨:常见的启发函数
以下假设当前待考察节点为n1(x1, y1),目标节点为n2(x2, y2)。
A:曼哈顿距离
曼哈顿距离其实是一种城市街区距离,即:类似城市街区那样,计算两个路口间的距离是按这两个路口坐标的水平差量与垂直差量的总和来算的。因此:h(n1) = abs(x2 - x1) + abs(y2 - y1)。曼哈顿距离计算公式经试验是较为适合街区类型地图的。
B:对角距离
如果地图允许往对角方向运动的话,则曼哈顿距离是需要考虑8个方向的。因此为简化计算,可以简单使用4个方向来代替。对角距离只取曼哈顿距离的其中差距较大的那个信息。因此:h(n1) = max(abs(x2 - x1), abs(y2 - y1))。
C:欧几里德距离
如果地图是允许往任意方向运动的话,则可以考虑使用两点间的直线距离。因此:h(n1) = sqrt(abs(x2 - x1)^2 + abs(y2 - y1)^2)。
在实际构造启发函数时,如果有可能还可以考虑待考察节点的朝向信息。即:考虑待考察节点(也就是当前节点)朝向是否与起始节点往目标节点朝向吻合。朝向越稳合,则说明该节点越应该被重视。因此,不妨可以为启发函数设定一个系数。节点的朝向越吻合,则系数越低;朝向越不吻合,则系数越高。这样启发函数的启发信息参考的就越多。
二:实现篇
探讨:开表、闭表
启发函数的不同构造,是在整体上影响着算法的路径选择走向,从而影响算法的整体性能。一旦启发函数明确后,则算法的整体走向是确定的,之后影响算法的效率问题就落在算法实现层面。这其中开表与闭表的设计就显的尤为重要,为何?(不明白为何的人,请再次查阅上一篇《A*算法入门》篇,直到看懂为止)。
开表在算法中的作用不单单只是为了存储待考察的节点,在整个算法动作过程中,需要频繁取出(开)表中f(n)值最小的那个,以及需要频繁确认当前节点的邻接节点是否已落在在开表中。而闭也也是需要频繁被查阅是否当前节点的邻接节点已经在闭表中。因此,开表、闭表的性能十分重要。此处只论开表,如果开表设计好了,自然了解如果设计闭表。
方案一:链表(不可取)
使用链表可以在存储上得到便利,而定位f(n)值最小节点时,效率奇低,因而绝对不可取。
方案二:平衡二叉搜索树(可取,综合性能很好,但还不是最好的)
如果应用该方案,则一般情况下会优先考虑rb-tree。该结构是被大量试验证明,其在插入、查找、删除方面性能都很高的一种数据结构,而且许多语言都有对它进行设计实现。
方案三:(小根)堆(可取,性能很好,但只限在定位或提取f(n)值最小节点方面)
在定位或提取f(n)值最小节点方面,堆方案绝对会比二叉搜索树来的高,但其在定位当前节点的邻接节点是否已在表中时,则只能遍历表数据,这方面性能就大不如方案二。
在实际中,个人还是更推荐堆方案。之前设计的C++版本的A*算法,采用的便是堆方案。在88 x 88无掩码地图格上,任意两点间的寻路耗时都不超过4ms。而且该实现版本是适用于任何类型地图的。(其实该版本还是有可优化的空间的,因为在估值函数方面,本人没有做任何优化。)
其实A*算法在存储开销上,也是需要重点注意的,不同的设计者,设计方案不同,效率肯定也不一样,此处不讨论。Ok,今天到此为止吧,有兴趣的同学,欢迎共同探讨。