A*算法详尽的入门教程
一: 为什么我们需要A*算法
求最短路径或者最小代价的算法有很多。其本质就是图的搜索策略。图的直接搜索方法有很多种,比较典型的是广度优先搜索、深度优先搜索。所谓的广度优先搜索是每到达一个节点就优先遍历该节点的所有相邻节点。而对应的深度优先搜索是指一直延伸到从未达到过的节点。基于以上两种基本思想的最短路径算法有Dijkstra算法和Floyd算法。当搜索完毕也遍历了整张图,其时间开销是很大的,尤其是在图非常大的时候这种时间复杂度是不能接受的。上述算法之所以时间开销大是因为在搜索的过程并没有对当前状态进行评估,因此是基于穷搜的。另一种思路就是在搜索的过程中利用一些合适的评估函数来进行剪枝,首先去除那些不可能产生最优解的分支。这就是所谓的启发式搜索。而A*算法就是基于这种思想的搜索算法。
二: 什么是A*算法
A*算法是一个可采纳的最好优先算法(BFS)。
A*算法的估价函数可表示为:
f‘(n)= g‘(n) + h‘(n)
这里,f‘(n)是估价函数,g‘(n)是起点到节点n的最短路径值,h‘(n)是n到目标的最短路经的启发值。由于这个f‘(n)其实是无法预先知道的,所以我们用前面的估价函数f(n)做近似。g(n)代替g‘(n),但 g(n)>=g‘(n)才可(大多数情况下都是满足的,可以不用考虑),h(n)代替h‘(n),但h(n)<=h‘(n)才可(这一点特别的重要)。可以证明应用这样的估价函数是可以找到最短路径的,也就是可采纳的。我们说应用这种估价函数的最好优先算法就是A*算法。
举一个例子,其实广度优先算法就是A*算法的特例。其中g(n)是节点所在的层数,h(n)=0,这种h(n)肯定小于h‘(n),所以由前述可知广度优先算法是一种可采纳的。实际也是。当然它是一种最坏的A*算法。
三: A*算法的过程
上节简单介绍了A*算法的基本原理。那么对于一个具体的问题怎么用A*算法呢?下面给出A*算法的执行过程。
1. 初始化
将开始节点放入开放列表(开始节点的F和G值都视为0);
2. 重复以下过程
① :在开放列表中查找具有最小F值的节点,并把查找到的节点作为当前节点;
② :把当前节点从开放列表删除,加入到封闭列表;
③ :对当前节点相邻的每一个节点依次执行以下步骤:
1.如果该相邻节点不可通行或者该相邻节点已经在封闭列表中,则什么操作也不执行,继续检验下一个节点;
2.如果该相邻节点不在开放列表中,则将该节点添加到开放列表中, 并将该相邻节点的父节点设为当前节点,同时保存该相邻节点的G和F值;
3. 如果该相邻节点在开放列表中, 则判断若经由当前节点到达该相邻节点的G值是否小于原来保存的G值,若小于,则将该相邻节点的父节点设为当前节点,并重新设置该相邻节点的G和F值.
④ 循环结束条件:
当终点节点被加入到开放列表作为待检验节点时, 表示路径被找到,此时应终止循环;
或者当开放列表为空,表明已无可以添加的新节点,而已检验的节点中没有终点节点则意味着路径无法被找到,此时也结束循环;
3.打印路径
从终点节点开始沿父节点遍历,
并保存整个遍历到的节点坐标,遍历所得的节点就是最后得到的路径;
四: A*算法的伪代码
while(OPEN!=NULL) { 从OPEN表中取估价值f(n)最小的节点n; if(n节点==目标节点) break; for(当前节点n的每个子节点X) { 算X的估价值; if(XinOPEN) if(X的估价值小于OPEN表的估价值) { 把n设置为X的父亲; 更新OPEN表中的估价值;//取最小路径的估价值 } if(XinCLOSE) continue; if(Xnotinboth) { 把n设置为X的父亲; 求X的估价值; 并将X插入OPEN表中;//还没有排序 } }//endfor 将n节点插入CLOSE表中; 按照估价值将OPEN表中的节点排序;//实际上是比较OPEN表内节点f的大小,从最小路径的节点向下进行。 }//endwhile(OPEN!=NULL)
五: A*算法的一个小例子
为了更形象的了解A*算法,我们举一个简单的求两点间的最短距离来了解A*算法的执行过程。
问题如上图,求从S->T的最短路径。A*算法的执行过程如下:
Step1:
Step2:
Step3:
Step4:
Step5:
Step6:
因为T是目标节点, 所以我们得到解:
S->V1 ->V4 ->T
六: 利用A*算法解决八数码问题
1. 问题描述
八数码问题也称为九宫问题。在3×3的棋盘,摆有八个棋子,每个棋子上标有1至8的某一数字,不同棋子上标的数字不相同。棋盘上还有一个空格,与空格相邻的棋子可以移到空格中。要求解决的问题是:给出一个初始状态和一个目标状态,找出一种从初始转变成目标状态的移动棋子步数最少的移动步骤。
所谓问题的一个状态就是棋子在棋盘上的一种摆法。棋子移动后,状态就会发生改变。解八数码问题实际上就是找出从初始状态到达目标状态所经过的一系列中间过渡状态。
八数码问题一般使用搜索法来解。搜索法有广度优先搜索法、深度优先搜索法、A*算法等。
下面用一张图即可表明八数码问题。
初始态 过渡态 终态
2. A*算法解决八数码问题
用A*算法解决八数码问题的核心是找出代价函数。
F(n) =g(n) + h(n)
f为实际路径(估价);
g为已经走过的状态数;
h为由状态n到达最终状态的困难程度(这里是与最终状态不同的个数)。
注意,这里的h为状态n与最终状态不同的个数,这也仅仅对于八数码问题合适,对于15数码和更多的问题就不适合了,需要寻找更好的估价了。
这里用一维数组分别代表从上至下,从左至右的排列顺序来代表某一状态。
有了这个以后我们就可以遵循A*算法的步骤来处理八数码问题了。
3. 算法的实现
Ⅰ 实验环境
硬件环境:PC
开发环境:VisualStudio 2013
开发语言:C++
Ⅱ 算法流程图
Ⅲ 数据结构
ⅰ 状态图的状态节点:
struct Node
{
int State[9];//该节点的状态
int Parent;//状态图中该节点的父节点
int h;//节点到达目的的困难程度
int g;//节点的实际路径
int f;//节点的总路径
};
ⅱ 保存当前状态数组:
static int End[9]; //目标状态
static int Start[9]; //初始状态
static int Invalid[9] = { 0, 0, 0, 0, 0, 0, 0, 0, 0 }; //无效的状态
ⅲ CLOSE和OPEN表
static Node OPEN[500];
static Node CLOSE[500];
Ⅳ 实验结果
测试数据:
初始状态 最终状态
共经历了4步,运行截图如下:
测试数据的手算算法过程如下:
可以看到和我们得到的结果是一样的,说明这个程序是没问题的。
Ⅴ 程序源代码
http://download.csdn.net/detail/simon_world/8303877