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中不存在

    码字不易

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

  • 相关阅读:
    我的知识库(4) java获取页面编码(Z)
    知识库(3)JAVA 正则表达式 (超详细)
    The Struts dispatcher cannot be found. This is usually caused by using Struts tags without the associated filter. Struts
    某人总结的《英语听力的技巧 》,挺搞的
    我的知识库(5)java单例模式详解
    构建可扩展程序
    SerialPort (RS232 Serial COM Port) in C# .NET
    Python学习笔记——String、Sequences
    UI题目我的答案
    jQuery学习系列学会操纵Form表单元素(1)
  • 原文地址:https://www.cnblogs.com/Cxiangyang/p/13230752.html
Copyright © 2011-2022 走看看