明明白白A*寻路,一定让你懂

原创 2013年09月09日 23:12:47

    当我们要对一张地图进行寻路时,首先要对这张地图打掩码,掩码可能会有多种,如可通过,不可通过,被挡住,沉下等。这取决于你的需求。我们以最简单的方式来介绍A*寻路。

(一)结构的设计,数据的存放

    A*寻路需要2张表,一张叫close表,里面存放的是你找到的可能适合你走到终点的格子,也就是说,你最后寻到的路的格子都在这个close表里面。一张叫open表,里面存放的是你找路过程中,尝试过的格子。

    我们先为地图画格子,然后给每个格子记个编号。我们为格子记个结构,这个结构很简单,它里面只有格子的掩码,如下:

struct  ST_GRID
{
        ST_GRID() { gfFlag = GRID_FLAG_DEFAULT; }
        GRID_FLAG gfFlag;    // 格子的掩码,这是一个枚举值
};
     我们用一个vector来存放所有格子的掩码,vector<ST_GRID> m_vecGrid;我们还需要一个用于寻路过程的结构,如下:

struct NODE
{
        NODE() { nIndex = 0; nG = 0; pChildNode = NULL; }
        int nIndex;           // 格子的索引
        int nG;               // 格子的G值
        NODE* pParentNode;    // 格子的父节点
};
    我们用一个vector来存放最终搜索到的路径。vector<int> m_vecPath;这个表里面的元素是格子的索引。接下来,我们定义两个局部变量,vector<NODE*> vecClose,这是close表,vector<NODE*> vecOpen,这是open表,表里存的是格子的索引。不要紧张你看到的G值和父节点的作用,后面会讲解G值是什么和父节点的作用。

(二)开始寻路

1)走第一步


    看上面的图,蓝色格子是障碍,红色格子是起点,绿色格子是终点,其他的都是可通过的格子,我们要做的就是从红色点寻路到绿色点。

    1、我们从红色点开始,因为是从它开始,就把它放入close表。并且,他的父节点指向NULL。周围的8个格子,除了86这个格子为不可通过之外,其他的都是可通过的,我们将它们全部放进open表。看下图,所有标白色的格子都是放入了open表的格子。


在放入open表之前,我们需要做一件事情,就是标识这个格子是从哪一个格子寻过来的。我们是从图中的红色格子搜索到周围8格的,所以,把父指针指向红色格子105。哦, 指针是C++的叫法,java,python等等的朋友,就叫引用。

    2、我们需要用几个值来确定下一个点从哪里开始。这是A星寻路的核心,F = H + G。在红色格子周围的7个格子里(86不可通过,已经被忽略),F值最小的,就是我们的下一个起点。我们把下一个起点叫X。

    G = 红色起点到X的距离。注意,G值永远都是起点到X点的移动量。我们把水平距离定为10,把对角距离定位14。即红色格子105与可通过格子106的距离是10,与可通过格子126的距离是14。我们定水平距离为10,根据勾股定理,对角等于14.14,我们取整数14,计算可以更快,误差也不大,是可以接受的。
    H = X到绿色终点的估值距离。以X为106为例子,这个距离等于X点106与绿色终点67的水平距离 + X点106与绿色终点67的垂直距离 = (1 + 2 )*10= 30,也就是相差的格子数 * 10,* 10是因为我们定义相邻两格的水平距离和垂直距离都是10。

    好了,H知道怎么算了,G也知道怎么算了,F = H + G,也可以算了。


    上图标出的黄色的数字,就是对应格子的F值。你算对了吗?我只标出85,86,106这三个格子,其他84,104,124,如果你有兴趣,可以算下哈。但是,你应该直眼就能看出,他们的F值应该超过40,因为他们距离绿色终点格子67都比较远。

    看86格,它的G值 = 红色格子105与自己86的距离,根据勾股定理,G值为14,它的H值 = 自己86与绿色终点67的水平距离+垂直距离 = 10 + 10 = 20,所以它的F值 = H + G = 20 + 14 = 34。当然,它是不需要被计算的,因为它是不可通过的格子,已经被忽略了,我只是举个例子。

    前面我说过,下一个起点格子,是F值最小的,那么格子85和106,他们的F值都是40,我们应该选谁呢?其实,在这里选谁都一样,当你在84,85,106,126,125,124,104之间算最小值得时候,你肯定会定义一个minF,然后将所有格子的F与minF比较,如果更小,你就会取那个格子的索引。这完全取决于你所用的算法。我们就假设106这个格子是你选到的下一个格子吧。


    看上面的图,我们选定了106,于是,他就进入了close表,我们用灰绿色表示。105,106这两个已经都在close表里了。

    我们继续来选下一个格子。

2)走第二步

    灰绿色格子106周围,86,87,107都不可通过,我们不管,以前红色的格子105,它已经在close表里了,close表,里面存放的是你找到的可能适合你走到终点的格子,既然已经进去了,那我们也不管这个格子了,85,125,126,127,都是可通过的格子,按照上面的叙述,我们应当将他们放进open表,对吧。127是新搜索出来的格子,直接加入open表,问题是85,125,126这几个格子,他们已经在open表里了,当我们以灰色格子106为起点时,他们再一次被检索出来,我们该怎么处理他们呢,直接不管吗?哦,不,不行,你应该这样操作。

    以85为例子。85之前的父格子是红色格子105,他的G值是10,现在它是灰色格子106选出来的,它与灰色格子106的G值是多少呢?是14吗?不是的,85与灰绿色格子106的G值应该是14 + 灰绿色格子与父格子之间产生的G值,也就是灰绿色格子106之前保存的G值,所以,应该是14 + 10 = 24。因为G值,是起点与当前格子之间的移动量,而不是新起点与当前格子之间的移动量。我们的比较是这样的,看谁的G值更小。85之前的G值是10,这次的G值是24,好吧,那还是按照之前的G值,也就是我们再一次的忽略他了。(假如,这里85原先的G值比现在的大的话,我们应该这样处理,改变85的G值,并将他的父格子转换为新的格子,也就是父格子从之前的105,转为灰色格子106,并且,G值为24。)

    显然,85格子是我们搜索到的下一个格子,我们将他放入close表。close表和open表看起来应该像下面这张图这样子。灰绿色格子在close表里,白色点所在格子都在open表里。

(三)寻到终点

   当你按照上面的方法进行搜索,到最后到达绿色点67时,你就应该终止你的寻路了。


参照上面的图,在close表里,你会有105,106,85,66,67这几个格子。并且,106的父格子是105,85的父格子是105,66的父格子是85,67的父格子是66。那么从67开始,沿着父格子找到起点,就是你寻出来的路了。最终,你会寻到的路如下图:

(四)A星寻路的核心代码示例:

void CGrid::FindPath()
{
    vector<NODE*> vecOpen;
    vector<NODE*> vecClose;
    m_vecPath.clear();

    if (m_nStartIndex == -1 || m_nEndIndex == -1)
    {
        return ;
    }

    // 起始点加入close表
    NODE *startNode = new NODE;
    startNode->nIndex = m_nStartIndex;
    startNode->nG = 0;
    startNode->pChildNode = NULL;
    vecClose.push_back(startNode);

    int nDeadLoop = 0;
    int a = 0;
    while(true)
    {
        if (nDeadLoop++ >= 200)
        {
            // 删除open表
            for (int i = 0; i < (int)vecOpen.size(); i++)
            {
                delete vecOpen[i];
            }
            vecOpen.clear();

            // 删除close表
            for (int i = 0; i < (int)vecClose.size(); i++)
            {
                delete vecClose[i];
            }
            vecClose.clear();

            m_vecPath.clear();
            MessageBox(m_pWnd->GetSafeHwnd(), "找不到路了", "不好了", MB_OK);
            return ;
        }
        int nIndex = vecClose[vecClose.size() - 1]->nIndex;
        if (nIndex == m_nEndIndex)      // 找到了终点就停止
        {
            break;
        }

        // 找新的点加入open表
        for (int nDir = 0; nDir < 8; nDir++)
        {
            int nNextIndex = this->GetNextIndexByDir(nIndex, nDir);
            if (nNextIndex < 0|| nNextIndex >= (int)m_vecGrid.size())
            {
                continue ;
            }

            if (this->GetFlag(nNextIndex) != GRID_FLAG_DEFAULT)
            {
                continue ;
            }

            // 已经在关闭列表里的就不用再拿出来找了
            if (IsInThisTable(vecClose, nNextIndex))
            {
                continue ;
            }

            // 已经在open表里,检查G值是否更小
            if (IsInThisTable(vecOpen, nNextIndex))
            {
                NODE *pNode = NULL;
                for (int i = 0; i < (int)vecOpen.size(); i++)
                {
                    if (vecOpen[i]->nIndex == nNextIndex)
                    {
                        pNode = vecOpen[i];
                        break;
                    }
                }
                if (!pNode)
                {
                    break;
                }
                int nG = vecClose[vecClose.size() - 1]->nG + GetGByIndex(nNextIndex, nIndex);   // 父节点G值加上自己的G值
                if (pNode && pNode->nG > nG)
                {
                    pNode->nG = nG;
                    pNode->pChildNode = vecClose[vecClose.size() - 1];
                }
                continue ;
            }

            NODE *nextNode = new NODE;
            nextNode->nIndex = nNextIndex;
            nextNode->nG = vecClose[vecClose.size() - 1]->nG + GetGByIndex(nextNode->nIndex, nIndex);
            nextNode->pChildNode = vecClose[vecClose.size() - 1];
            vecOpen.push_back(nextNode);
        }

        // 找open表里F值最低的
        int nMinF = 0xFFFFFF;
        int nMinIndex = 0;
        int nDestX = m_nEndIndex/ this->GetStageCol();
        int nDestY = m_nEndIndex % this->GetStageCol();
        for (int i = 0; i < (int)vecOpen.size(); i++)
        {
            int nX = vecOpen[i]->nIndex / this->GetStageCol();
            int nY = vecOpen[i]->nIndex % this->GetStageCol();

            int nH = (abs(nDestX - nX) + abs(nDestY - nY))*10;
            int nG = vecOpen[i]->nG;
            int nF = nH + nG;
            if (nF < nMinF)
            {
                nMinF = nF;
                nMinIndex = i;
            }
        }
        if (nMinIndex >= 0 && nMinIndex < (int)vecOpen.size())
        {
            vecClose.push_back(vecOpen[nMinIndex]);
            vecOpen.erase(vecOpen.begin() + nMinIndex);
        }
    }

    // 寻路完成,找最优路径
    NODE* pEndNode = vecClose[vecClose.size() - 1];
    while(pEndNode && pEndNode->nIndex != m_nStartIndex)
    {
        m_vecPath.push_back(pEndNode->nIndex);
        pEndNode = pEndNode->pChildNode;
    }
    m_vecPath.push_back(pEndNode->nIndex);

    // 删除open表
    for (int i = 0; i < (int)vecOpen.size(); i++)
    {
        delete vecOpen[i];
    }
    vecOpen.clear();

    // 删除close表
    for (int i = 0; i < (int)vecClose.size(); i++)
    {
        delete vecClose[i];
    }
    vecClose.clear();

    m_pWnd->Invalidate();
}
    上面这份源代码,就是A星寻路的主要逻辑,是不是很简单呢?嘿嘿。。。如果你参考网上其他文章,搞了很久都搞不出来,或者找不出最优解,一般问题都出在对G值的判断与处理上,请参考本文有关G值描述的地方,相信你很快就写出自己的A星寻路。用黄色背景突出,只是希望找不到最优解的你能驻足一下,嘿嘿~~~

(五)几种典型图的寻路结果


好了,A星寻路算法就写到这里了,有疑问的朋友请留言哈~~~

unity3d多人寻路问题方案

转自:http://tieba.baidu.com/p/3397037553 相信大家用unity3d自带navmeshagent寻路的时候,一定会碰到多人寻路相互挤压的问题。 这个我已经解决了,...
  • u011926026
  • u011926026
  • 2017年03月19日 10:59
  • 589

A*寻路算法——多人寻路、实时碰撞寻路、最近目的地

A* 路算法原理可以参考这个文章,已经写的很详细了http://www.cppblog.com/mythit/archive/2009/04/19/80492.aspx 这篇文章主要写写多人寻路的实...
  • UnSkyToo
  • UnSkyToo
  • 2015年11月04日 21:34
  • 3189

Unity3D中的自动寻路

Unity3d自动寻路基础教程
  • grf123
  • grf123
  • 2017年01月07日 18:31
  • 646

a*自动寻路算法详解

这篇博文是在其他博客基础上加工的,主要原因是感觉原博客举得例子不太好,很多细节感觉没有描述。 A*算法主要是在父节点更新那个地方很容易误解,但是父节点的更新又是A*算法的核心,因为遍历到目标节点之后就...
  • jialeheyeshu
  • jialeheyeshu
  • 2016年11月09日 20:47
  • 907

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

本文源码下载地址:http://download.csdn.net/detail/sun2043430/5907609 源起 最近回头温习云风的书,看到A*寻路算法,这个算法也是我一直想学习实现一下的...
  • sun2043430
  • sun2043430
  • 2013年08月11日 16:21
  • 3372

Unity3D 5.3 入门学习之寻路组件一

搭建一个测试场景,放了一个摄像机(Main Camera),一个充当地面的面片(Plane),一个充当阻碍物(Obstacles),一个充当玩家的球体(Player)和一个寻路目标点物体(Target...
  • u012606320
  • u012606320
  • 2016年01月14日 22:48
  • 1258

人工智能: 自动寻路算法实现(一、广度优先搜索)

前言随着人工智能技术的日益发达,我们的生活中也出现了越来越多的智能产品。我们今天要关注的是智能家居中的一员:扫地机器人。智能扫地机器人可以在主人不在家的情况下自动检测到地面上的灰尘,并且进行清扫。有些...
  • u012907049
  • u012907049
  • 2017年07月11日 16:01
  • 1679

游戏寻路算法的简单实现

提到寻路算法,大家都会想到A*算法。 在度娘找了不少代码,看了不少教程之后,尤其是这个文章中提到的总结:http://www.cppblog.com/christanxw/archive/2006/...
  • Gnorth
  • Gnorth
  • 2013年06月19日 14:53
  • 2480

Unity3D实现A*寻路算法

目录(?)[+] A算法复习实现NodePriorityQueueGridManagerAStarTestCode ClassScene setupTesting总结 ...
  • IT_faquir
  • IT_faquir
  • 2015年05月05日 15:55
  • 2500

Java算法--寻路

题目: 要求用户输入一个值n作为一个n*n的矩阵大小,然后用户输入n行,每行有n个字符,每个字符用空格隔开,其中字符“A”表示起点,字符“B”表示终点,中间寻路有要求,如果当前字符是“+”则下一步必须...
  • ChamPly
  • ChamPly
  • 2015年06月01日 07:49
  • 774
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:明明白白A*寻路,一定让你懂
举报原因:
原因补充:

(最多只允许输入30个字)