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

码字不易

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

posted @ 2020-07-03 15:36  乐swap火  阅读(475)  评论(0编辑  收藏  举报