zoukankan      html  css  js  c++  java
  • A星算法-小结

    在游戏中常常会需要使用到寻路算法,常用的有深度寻路、广度寻路、A*寻路等。这些算法都集结了前人的智慧,作为程序员,我们的责任是把这些算法以代码的形式表示出来。本篇记录这两天遇到的A*寻路算法的简易实现。

    基础的理论知识网上有很多,这里不再赘述,需要了解的概念有以下几个:

    1. F值:起点到终点的距离。
    2. G值:起点到当前点的距离(预估值,会变化)。
    3. H值:当前点到终点的距离(可以认为是一个常量,到一个确定坐标的点,其曼哈顿距离也就是确定的)。

    公式:F = G + H, 直线走一格为10,斜线走一格为14(勾股定理取近似值)

    1. 曼哈顿距离:其实就是两个点间相差的横纵坐标之和(具体理解->直角三角形的两边和)。
    2. Openlist:存放可以走的点的集合,下一步要走的点在这个集合中找。简单说就是,从环绕“当前点”周围的八个点中去除不符和条件的点后,剩余的点,记住每走一步都需要检测这八个点,符合条件就添加到openlist中。

    添加入openlist的条件:1.不在closelist 中 2.不是障碍点3.不在openlist(已经存在自然就不能重复添加啦)但是需要注意的是,如果这个点当前计算出的G值比原来的G值小,则原来的G值和F值需要替换掉,父节点也改为当前点,这里可能比较难理解,但是想一想,该点当前在openlist中,那它就有可能成为下一个“当前点”,那么如果它的G值(起点到当前点的距离)可以更小,那就必须改变,因为A*的目的就是要找最短路径。

    1. Closelist:这个表存放所有走过的点和障碍点(在检测八个点的时候加入)。

    注意:1.openlist需要定时删除“点”,要怎么理解呢,找下一个目标点需要在openlist中找,它的条件就是F值最小,当找到了这个点后,用一个变量currNode记住它,然后将它从openlist中删除;走过的点就应该删除,否则可能再次选到。

    1. 7.      prev:父节点;这一个概念可能网上别的A*资料没说到,但是它却十分重要,构成地图的点除了有f、g、h这三个属性外,还必须有这个prev属性,它的作用就是将终点和起点间的最短路径连接起来。首先,起点的prev自然就是null,而下一个点(我称其为A)的prev就是起点,再下一个点(B)的prev就是A,依此类推,直到终点,最短路径就是根据终点的prev依次往前推得到;在前边讲openlist时说过,往openlist中添加点时发现该点已经存在,则需要比较我们即将添加入openlist中的点和openlist中已经存在的点,它们两个的G值,如果发现即将添加进去的点G值比较小,则将openlist中的点G值赋值为小值,且改变它的父节点为当前点;(这个叙述可能会使一些网友摸不着头脑,想象一下,当一个点向正下方移动一步时,原点周围的八个点和当前点周围的八个点是不是重合了6个,所有就会出现往openlist中添加点的时候发现它已经存在;还有为什么要将障碍点添加到closelist中呢,原因就是不能让障碍点被判断两次,完全是浪费资源)

     

    说了一大堆,可能用代码来表达简洁多了,使用c语言完成实现

    //头文件

    #ifndef ASTAR_H

    #define ASTAR _H

    #define COL_X 9  //9列7行的地图

    #define ROW_Y 7

    //a星节点

    struct AStarNode

    {

          unsigned int f_; //f值,起点到终点

          unsigned int g_;//g值,起点到当前点(预估值)

          unsigned int h_;//h值,当前点到终点

          int x; //地图坐标

          int y;

    };

    //枚举了八个方向,作用是区别偏移量,方便理解使用枚举

    //也可以用下边三个数组快速计算偏移量(顺时针),xy下标一一对应,可以减少冗余代码

    //AROUND_X = {0, 1, 1, 1, 0, -1, -1, -1};  //顺时针,由正北开始

    //AROUND_Y = {-1, -1, 0, 1, 1, 1, 0, -1};

    //AROUND_G = {10, 14, 10, 14, 10, 14, 10, 14}; //环绕的八点,g值

    enum dir

    {

          dir_b, //北

          dir_d,//东

          dir_n,//南

          dir_x,//西

          dir_xb,//西北

          dir_db,//东北

          dir_dn,//东南

          dir_xn,//西南

    };

    #endif

     

    //cpp文件

    #include <vector>

    #include <math.h>

    #include <stdlib.h>

    #include <windows.h>

    #include “aStar.h”

    using namespace std;

    //对于openlist来说,每走一步都要删除上一个走的节点,使用vector不是一个好方法,需要改进可以使用map容器,(设置键值的一种方法可以根据xy坐标生成字符串)

    vector<AStarNode*>  AStar_Closelist;

    vector<AStarNode*>  AStar_Openlist;

    AStarNode* begin_Node; //开始节点

    AStarNode* end_Node;//结束节点

    AStarNode* current_Node;//当前节点

     

    int aStar_Node[ROW_Y][COL_X] = {

    {1, 1, 1, 1, 1, 1, 1, 1, 1,},

    {1, 0, 0, 0, 1, 0, 1, 1, 1, },

    {1, 0, 1, 0, 0, 0, 1, 1, 1, },

    {1, 0, 1, 0, 1, 0, 1, 0, 1, },

    {1, 0, 0, 0, 1, 0, 1, 0, 1,},

    {1, 0, 1, 0, 1, 0, 0, 0, 1, },

    {1, 1, 1, 1, 1, 1, 1, 1, 1,}

    };

    //设置地图起点和终点

    void setBeginAndEnd(int b_y, int b_x, int e_y, int e_x);

    //找A星路径,参数isAngle表示是否支持走斜线,默认支持

    void findRoute(AStarNode* current_node, bool isAngle = true);

    //判断节点是否在openlist上

    bool isOpenList(AStarNode* node, AStarNode* current_node);

    //删除openlist上的点

    void removeOpenListNode(AStarNode* node);

    //判断节点是否在closelist上

    bool isCloseList(AStarNode* node);

    //main函数

    int main(void)

    {

          setBeginAndEnd(3, 1, 3, 7);

          //当前点初始化为开始点

          current_Node = begin_Node;

          //将当前点放入closelist

          AStar_Closelist.push_back(current_Node);

          //寻找路径

          findRoute(current_Node);

          system(“pause”);

          return 0;

    }

    //设置地图起点和终点

    void setBeginAndEnd(int b_y, int b_x, int e_y, int e_x)

    {

          begin_Node = new AStarNode;

          memset(begin_Node, 0, sizeof(AStarNode));

          begin_Node->x = b_x;

          begin_Node->y = b_y;

          begin_Node->dir = dir_xb; //初始化方向为西北

          begin_Node->prev_ = NULL; //起点的父节点为null

          //终点

          end_Node = new AStarNode;

          memset(end_Node, 0, sizeof(AStarNode));

          end _Node->x = e_x;

          end _Node->y = e_y;

    }

    //判断节点是否在openlist上

    bool isOpenList(AStarNode* node, AStarNode* current_node)

    {

          vector<AStarNode*>::iterator it = AStar_Openlist.begin();

          for(it; it != AStar_Openlist.end(); ++it)

          {

                 //在openlist中发现该点已经存在

                 if((*it)->x == node->x &&(*it)->y == node->y)

                 {

                        //如果该点的g_比较大,则改变它的g_

                        if((*it)->g_ > node->g_) {

    (*it)->g_ = node->g_;

    (*it)->f_ = (*it)->g_ + (*it)->h_;

    (*it)->prev_ = current_node;  //重新指定父节点

    }

    return true;

    }

    }

    return false;

    }

     

    //删除openlist上的点

    void removeOpenListNode(AStarNode* node)

    {

          vector<AStarNode*>::iterator it = AStar_Openlist.begin();

          for(it; it != AStar_Openlist.end(); ++it)

          {

           if((*it)->x == node->x && (*it)->y == node->y)

           {

           AStar_Openlist.erase(it);

           break;

    }

    }

    }

    //判断节点是否在closelist上

    bool isCloseList(AStarNode* node)

    {

          vector<AStarNode*>::iterator it = AStar_Closelist.begin();

          for(it; it != AStar_Closelist.end(); ++it)

          {

           if((*it)->x == node->x &&(*it)->y == node->y)

           {

           return true;

    }

    }

          return false;

    }

    void findRoute(AStarNode* current_node, bool isAngle = true)

    {

          AStarNode* currNode = current_node;

          bool flag = true;

          while(flag)

          {

                 AStarNode* minP = new AStarNode; //储存最优点(下一步要走的点)

                 memset(minP, 0, sizeof(AStarNode)); //初始化节点

                 if(AStar_Openlist.empty())

                 {

                        minP->f_ = 10000; //如果openlist中没有点,设置一个大值暂时占位,这个情况出现于当前点为起点时

    }else{

           minP = AStar_Openlist[0]; //将最优点先赋值为openlist的第一个元素

    }

    //试探方向

    for(int i = 0; i < (isAngle ? 8 : 4); ++i)

    {

        AStarNode* node = new AStarNode;

        memset(node, 0, sizeof(AStarNode));

    /*node->x = currNode->x + Around_X[i];

        node->y = currNode->y + Around_Y[i];

    node->g_ = currNode->g_ + Around_G[i];*/

    //上述注释的部分可以替换下边这个switch语句

    //不过isAngle的功能就需要做一下修改

    //短短的三行语句,功能相同,但是却减少了大量冗余代码

        switch(i)

        {

               case dir_xb:

                      node->x = (currNode->x)-1;

                      node->y = (currNode->y)-1;

                      node->g_= currNode->g_ + 14;

                      break;

               case dir_b:

                      node->x = (currNode->x);

                      node->y = (currNode->y)-1;

                      node->g_= currNode->g_ + 10;

                      break;

               case dir_db:

                      node->x = (currNode->x)+1;

                      node->y = (currNode->y)-1;

                      node->g_= currNode->g_ + 14;

                      break;

               case dir_d:

                      node->x = (currNode->x)+1;

                      node->y = (currNode->y);

                      node->g_= currNode->g_ + 10;

                      break;

               case dir_dn:

                      node->x = (currNode->x)+1;

                      node->y = (currNode->y)+1;

                      node->g_= currNode->g_ + 14;

                      break;

               case dir_n:

                      node->x = (currNode->x);

                      node->y = (currNode->y)+1;

                      node->g_= currNode->g_ + 10;

                      break;

               case dir_xn:

                      node->x = (currNode->x)-1;

                      node->y = (currNode->y)+1;

                      node->g_= currNode->g_ + 14;

                      break;

               case dir_x:

                      node->x = (currNode->x)-1;

                      node->y = (currNode->y);

                      node->g_= currNode->g_ + 10;

                      break;

    }

       

    //在closelist中

    if(isCloseList(node))

        continue;

    //障碍点,放入closelist

    if(aStar_Node[node->y][node->x] == 1){

               AStar_Closelist.push_back(node);

    continue;

    }

    //在openlist中

    if(isOpenList(node, currNode))

        continue;

    //添加到openlist前的赋值操作

    //曼哈顿距离计算

    node->h_ = (abs(end_Node->x – node->x) +abs(end_Node->y – node->y))*10;

    node->f_ = node->g_ + node->h_; //起点到终点距离

    node->prev_ = currNode; //指定父节点

    //添加

    AStar_Openlist.push_back(node);

    }

    //遍历完八个点后,开始找目标点(下一个要走的点)

    //取openlist这f值最小的

    int count = AStar_Openlist.size();

    //openlist中没有点就说明,地图所有的点都已经走过

    if(count == 0){

                  cout<<”没有正确路径”<<endl;

                  return;

    }

    for(int i = 0; i < count; i++)

    {

        if(minP->f_ > AStar_Openlist[i]->f_)

        {

           minP = AStar_Openlist[i];

    }

    }

    //判断新的当前点是否等于终点

    if(minP->x == end_Node->x && minP->Y == end_Node->y){

        cout<<”找到正确路径”<<endl;

        AStarNode* pNode = minP;

    //从尾到头打印最短路径

        while(pNode)

    {

           cout<<”x:”<<pNode->x<<”y:”<<pNode->y<<endl;

           Sleep(500);//休眠0.5秒

           pNode = pNode->prev_; //依靠父节点依次向前推

    }

    return;

    }

                           //删除openlist中的当前点

    removeOpenlistNode(minP);

    //当前点添加入closelist

    AStar_Closelist.push_back(minP);

    currNode = minP; //下一轮找点开始

    }

    }

    //代码是手打上去的,可能存在一些小错误,刚开始了解A*的时候也是看了许多资料,后来发现只有通过自己亲自实现才真正明白,代码不重要,重要的是编程思想

     

     

    1代表障碍物,0表示通路;防止出现越界的情况,所以地图外圈都是障碍物

    1

    1

    1

    1

    1

    1

    1

    1

    1

    1

    0

    0

    0

    1

    0

    1

    1

    1

    1

    0

    1

    0

    0

    0

    1

    1

    1

    1

    0

    1

    0

    1

    0

    1

    0

    1

    1

    0

    0

    0

    1

    0

    1

    0

    1

    1

    0

    1

    0

    1

    0

    0

    0

    1

    1

    1

    1

    1

    1

    1

    1

    1

    1

    游戏地图

    以上就是关于A*算法的代码实现,存在许多不足,可继续优化,但基本上实现了该算法。

    结论:

    1. A*路径是通过最后一个点的父节点依次向前找出来的;该路径存在于closelist中,需要知道closelist中还存在着许多点,所有父节点的作用尤为重要。
    2. 每走一步之前,都要将选中的点从openlist中删除。
    3. 添加入openlist的条件是 1.不在closelist中,以及2.不是障碍点,3.openlist中不存在

    码字不易

    转载请说明文章的来源、作者和原文的链接。

  • 相关阅读:
    扫雷游戏:C++生成一个扫雷底板
    为《信息学奥赛一本通》平反(除了OJ很差和代码用宋体)
    关于最大公约数欧几里得算法辗转相除及最小公倍数算法拓展C++学习记录
    【Python】如何用较快速度用Python程序增加博客访问量——CSDN、博客园、知乎(应该包括简书)
    STL学习笔记之next_permutation函数
    C++集合容器STL结构Set
    11111111111
    express + mysql实践
    express实践
    ws:一个 Node.js WebSocket 库
  • 原文地址:https://www.cnblogs.com/Cxiangyang/p/13230752.html
Copyright © 2011-2022 走看看