A*寻路算法讲解+源码DEMO演示

本文源码下载地址:http://download.csdn.net/detail/sun2043430/5907609(第一版)
               http://download.csdn.net/detail/sun2043430/5909315(第二版)
               https://github.com/sun2043430/A_Star_Algorithm (github下载地址,推荐到这里下载,这里是最新版)
本文地址:http://blog.csdn.net/sunnianzhong/article/details/9899359

源起

最近回头温习云风的书,看到A*寻路算法,这个算法也是我一直想学习实现一下的。趁着周末有空练习练习。


网上关于A*算法的文章、代码和示例已经相当多了,有很多文章写的都很好,还有很多国外的网页用JS动态演示了A*算法寻路的过程。我找到的一些资源如下:

1 http://www.raywenderlich.com/zh-hans/21503/a%E6%98%9F%E5%AF%BB%E8%B7%AF%E7%AE%97%E6%B3%95%E4%BB%8B%E7%BB%8D

点评:生动的例子,讲解的非常浅显,易上手的示例。(入门级)

2 http://theory.stanford.edu/~amitp/GameProgramming/

点评:长篇大作。非常详细地介绍了A*算法的原理,演化过程,启发式,算法实现,A*实现中各种数据结构的优劣,A*算法变种,实际使用A*算法会遇到的问题。(深入级)

3 http://www.ccg.leeds.ac.uk/people/j.macgill/xaStar/

点评:使用java脚本制作的动态演示A*寻路算法的网页。演示了经典A*算法和Fudge A*算法 的动态寻路过程。(很有参考价值)

我用MFC模拟了经典A*算法的动态寻路过程,基本上模仿了这个网页。

感谢以上文章的作者和云风的书!


也说A*算法

A*算法是Dijkstra算法和贪婪算法的综合,Dijkstra算法的缺点在于从起点全方位360地向外做广度优先搜索,导致遍历节点太多,速度较慢,优点是能够保证找到最优路径。贪婪算法总是选择看起来最优的路线前进,优点是速度很快,缺点是有可能掉入陷阱,而走冤枉路。而A*算法采用启发式的方式,综合了二者的优点,且依然能够保证找到最优路径

以一个最简单的例子来说明一下A*算法的启发式寻路过程,顺便介绍一些A*算法中的概念。

A*寻路算法讲解+源码DEMO演示_第1张图片

以上图为例,红色为起点,绿色是终点。我们假设每一个方块只能直接到达其上下左右四个方块,不可以走斜线。每一个方块有3个值f,g,h(左上角是f值,左下角是g值,右下角是h值),其中:

  1. g是指从起点走到这个方块所经历的步数(这个是精确的值,是一步一步走过来时,累加得到的值);
  2. h是指从当前方块到终点方块的启发式步数(一般简单的就是假设该方块到终点之间没有障碍物,可到达的最小步数,这是理想值);
  3. f是g+h得到的值。

首先,从起点出发,可以往上下右3个反向走,所以上下右3个方块的g值为1。但是上下两个方块到终点的启发式步数h是7(横向6步,竖向1步),右边方块的启发式步数h是5。将g和h相加得到f值。

那么接下来,我们就选择f值最小的方块(右边的方块)来继续寻路过程……具体算法我们在下面给出。


A*算法具体过程

1 开两个空队列OPEN,CLOSE

2 将起点的g值设为0,并计算出对应的h,f值,将起点放入OPEN队列

3 从OPEN队列中取出f值最小的节点放入临时队列TempQueue(一开始就是起点这一个点;另外f值最小的可能不止一个,如有多个则都取出)(取出的意思是OPEN队列中就不再保存这些节点了)

4 如果TempQueue队列为空,也就意味着OPEN队列为空,说明已经遍历完整个地图,此时应该退出寻路过程【退出条件1:地图遍历完,走不到终点】

5 将TempQueue中的所有节点加入到CLOSE队列中

6 遍历TempQueue中的每一个节点Node

      处理Node的每一个邻居节点Neighbor

          如果Neighbor既不在OPEN队列也不在CLOSE队列中,计算出Neighbor节点的g,h,f值g值等于Node的g值加一个单位,设置Neighbor的父节点为Node,并将Neighbor加入到OPEN队列

          如果Neighbor在CLOSE队列中则不处理(在动态的游戏中可能需要看情况做处理,简单情况下无需处理)

          如果Neighbor在OPEN队列中,则用Neighbor之前的f值和现在计算出来的f值进行比较。如果现在计算出来的f值更小,则更新Neighbor的f值,并重新设置Neighbor的父节点为Node

          如果Neighbor就是终点,则寻路过程结束,从终点根据父节点的指向到CLOSE队列中去找父节点,可倒推出完整的最短路径【退出条件2:走到终点】

7 回到步骤3继续处理


A*算法关键代码

void CAStar::FindPath()
{
    m_bFind = false;
    vector minVec;
    while (!m_bFind)
    {
        minVec.clear();
        GetMinFromOpen(minVec);
        if (minVec.empty())
            break;
        m_close.insert(m_close.end(), minVec.begin(), minVec.end());
        for (vector::iterator it = minVec.begin(); it != minVec.end(); it++)
            DoNeighbors(*it);

        if (m_bFind)
            GetFinalShortestPath();

        if (m_CallBack) m_CallBack(m_pArg);
    }
}

DEMO程序的使用

DEMO程序运行界面如下

A*寻路算法讲解+源码DEMO演示_第2张图片

在右边3个单选按钮中选择一个,然后可在左边设置起点、终点或者障碍物。

点击开始寻路,则开始动态演示寻路过程。

其中:

红色方块为起点

绿色方块为终点

蓝色方块为OPEN队列中的点

黄色方块为CLOSE队列中的点

紫色方块为最短路径

计算过f,g,h值的节点,其f值在左上角;g值在左下角;h值在右下角。


请原谅本人糟糕的MFC编程。画面预显示做的很不好,不知道该在哪个消息中预绘制图形,而且因为没有使用双缓冲绘图,所以画面闪烁厉害。另外,寻路过程没有使用多线程,所以寻路过程中画面会卡住不能移动,这些方面希望在下一版中能够改进。另外我打算在下一个版本中加入可斜向移动的功能。

本文地址:http://blog.csdn.net/sunnianzhong/article/details/9899359

 
  

Update:(2013.8.12) 斜向移动功能

上传了第二版的代码,可在这里下载:http://download.csdn.net/detail/sun2043430/5909315
1 增加了斜向移动的功能,对应的每一步移动的权值进行了修改,平行或竖直移动一格的权值是5,斜向移动的权值是7。斜向移动时需要判断是否会穿过障碍物方块。如下的情况是不允许的。
   1    2
   3    4
只要1或者4位置是障碍物,那么从2到3的斜向移动是不允许的。
2 采用内存DC缓冲方式,画面先画到内存DC,再一次性 BitBlt 贴到控件上,使得画面不再闪烁。
3 寻路过程放到一个独立的线程中去运算,使得主界面不再被卡住。
4 增加寻路过程中的暂停、继续功能。更方便查看中间状态。




你可能感兴趣的:(算法)