路径搜索漫谈和A*算法简明实现
对于图的路径搜索问题,有许多著名的算法。比如耳熟能详的Dijkstra最短路径算法,由于他的证明严密又是由计算机祖师级的人发明的所以经常出现在教材中。该算法的核心思路是通过按照最短路径递增的顺序生成所有从原点到某一点的路径。举个具有启发性的例子,假设我们已经知道了从原点O到除了终点D之外所有其他点的最短路径,那么该如何求最后一步呢?现在考察所有与D相连的点,比如只有两个,A和B,那么因为已经知道从O到A和B的最短路径,只要取O..AD和O..BD之间较小的一个就是最优解,并且不存在其他可能,因为如果发现有另外一条路径(O..A)*不同于之前的O..A但是却能使从O到D之间的距离更短,那么也就意味着O..A就不是最短的了和我们之前的假设矛盾了(最短路径总是存在的)。
现在把这个思路一般化,把已知解的所有点作为一个集合,通过每次算出一个未知点的最短路径来扩大这个集合,那么最后就能得到所有最短路径。这里不做严格的算法描述,只是通过举例子展示一下基本的流程。如果已知从O出发的最短路径的终点有{O,A,B},未知的点有{C,D,E}。假设C,D,E和O,A,B之间存在某些连接方式,找出所有可能,其中最短的路径就是最优解了。比如OAC是这样一个最短的路径(A,B只是终点并记录最短路径值并不需要记住路径),那么不可能有其他路径到C比这个更短。假如有更短的路径O..XY..C,X属于已知点,Y属于未知点(这个形式是必然的)。如果X=A,Y=D,根据前面的假设OAC比OAD短,OAD..C是绝对不可能比OAC更短的。XY换作其他的任意值依然可以得出相同的结论。所以现在已知点的集合变成了{O,A,B,C}而未知点的集合为{D,E}。重复上述过程直到未知集合为空就求出了所有点到O的最短路径了。(细节请参见Dijkstra的链接)
虽然可以保证得到最短路径,但是Dijkstra的效率不高,最坏情况下复杂度为O(点2),而且实现起来也颇为复杂。相比之下A*算法显得简单高效,尽管不是每次都能找到最优解但是在平面图的搜索中许多时候次优解还是可以接受的。先解介绍一下A*的指导思想。该算法的关键在于对于每个点进行两方面的评估:实际和预估。实际代表了已经花费的路程,预估代表还有可能剩的路程,两者之和成为既有现实成分又有未知因素的总路程。举个例子,我们从中国乘飞机去美国,中途转机,已知到日本花费500,到英国花费1000,可是我们无法准确知道从日本或者英国到美国的价格。根据基本的地理知识,我们完全可以估计因为英国离美国近很多,所以英国转机的最终费用会更低并且差价大于500。这样一个分析就帮助我们选择在英国转机这样一个可能更经济的方案了(事实可能未必,取决于实际从英国,日本到美国的机票)。
这里有一篇非常详细的教程
现在来看一下A*算法,因为整个流程简单易懂,我们用自然语言描述:
1.初始化一个openlist和closelist。每个点拥有实际值G,估计值H,两者之和的最终值F,以及还原路径之用的父节点
2.将起始点放入openlist
循环{
3.取openlist的拥有最小F值的点node,加入closelist
4.如果node是终点或者openlist为空,退出循环
5.对于每一个node的不是障碍物也不在closelist的邻居点neighbour。
a.如果第一次访问,neighbour的父节点指向node并计算G,H,F值,然后放入openlist。
b.否则的话,求出G'=neighbour.G+{从node到neighbour的距离},F'=G'+neighbour.H。
如果F'<neighbour.F,根据刚才乘飞机的分析,选择从node出发到neighbour再到终点可能跟短,所以将neighbour的父节点指向node并计算G,H,F值。
继续循环。
}
6.退出循环后,如果存在解,根据终点的父节点按图索骥构成路径。
为了说明A*具有的实现简单和效率优势,以下是silverlight3编的小程序,如果你查看源代码会发现,主体算法和以上的伪代码基本一致,并且只有不到60行(比较一下Dijkstra的实现)
- Dijkstra, http://en.wikipedia.org/wiki/Dijkstra's_algorithm
- A* search algorithm, http://en.wikipedia.org/wiki/A*_search_algorithm
- A*寻路初探, http://data.gameres.com/message.asp?TopicID=25439
- Silverlight动画及游戏系列教程, http://www.cnblogs.com/alamiye010/archive/2009/06/17/1505339.html