#include <queue> #include <vector> #include <iostream> using namespace std; //方向向量 int direc[4][2]={{0,1},{-1,0},{0,-1},{1,0}}; //封闭,开放列表标记 enum Flag { SEAL, OPEN, UNVISITED }; //===========最小堆节点类============ //堆优先级为:F=G+H //G为当前的路径长 //H为估计当前位置到目标位置开销探测:当前节点(x,y),目标节点(x0,y0),则以曼哈顿距离作为H,H=|x-x0|+|y-y0| typedef struct node { int _x,_y;//节点坐标(x,y) int _G;//实际已开销G int _H;//探测将开销H int _F;//优先级_F=_G+_H struct node *pre; //前驱顶点 }Queue_Node; //_seal[][]结构体,保存结点的开放(封闭)标记,保存开放结点的指针 typedef struct { Flag flag; Queue_Node *point; }Seal; //=============A*算法类============= //类成员列表如下: //1.A*算法核心 //2.计算曼哈顿距离H函数 //3.迷宫输入函数 //4.最优解路径输出函数 //5.封闭列表 //6.最小堆实现开放列表 //7.迷宫长,宽 //8.迷宫地图 //9.起点,目标点 //10.最小堆排序函数 //11.绝对值函数 //12.边界判断 //13.打印路径 class A_Star { public: //构造函数,打印欢迎信息 A_Star() { cout<<"您正在使用的是入门级A*算法求解迷宫"<<endl; cout<<"算法采用广度优先搜索,以及探测方法优先选择开销最小的状态节点进行拓展"<<endl; cout<<"下面,您应该根据提示输入各种迷宫的信息,并请求调用A*算法求解"<<endl; input(); } //析构函数 ~A_Star() { for(int i=1;i<=_len;++i) { for(int j=1;j<=_wid;++j) { if(_seal[i][j].point!=NULL) { delete _seal[i][j].point; } } } for(int i=0;i<=_len;++i) { delete []_seal[i]; delete []_maze[i]; } delete []_seal; delete []_maze; } //迷宫输入函数 void input() { cout<<"输入: 迷宫左边长,上边宽! 例如:5 3"<<endl; cin>>_len>>_wid; _seal=new Seal*[_len+1]; _maze=new unsigned char*[_len+1]; for(int i=0;i<=_len;++i) { _seal[i]=new Seal[_wid+1]; _maze[i]=new unsigned char[_wid+1]; } cout<<"从下一行开始输入迷宫信息"<<endl; for(int i=1;i<=_len;++i) { for(int j=1;j<=_wid;++j) { cin>>_maze[i][j]; _seal[i][j].flag=UNVISITED; _seal[i][j].point=NULL; } } cout<<"输入起点坐标,目标点坐标,例如1 2 3 4"<<endl; cin>>_sx>>_sy>>_ex>>_ey; if(_maze[_sx][_sy]=='1'||_maze[_ex][_ey]=='1'||bound(_sx,_sy)==false||bound(_ex,_ey)==false) { cout<<"不可能存在这样的情况!"<<endl; return; } cout<<"调用A*算法打印结果如下:"<<endl; A(); } //A*核心算法 void A() { //源点入开放列表 Queue_Node *p_node=new Queue_Node; p_node->pre=NULL; p_node->_H=get_H(_sx,_sy); p_node->_G=0; p_node->_x=_sx; p_node->_y=_sy; p_node->_F=p_node->_H+p_node->_G; _open.push(p_node); _seal[_sx][_sy].flag=OPEN; _seal[_sx][_sy].point=p_node; //A*搜索 while(!_open.empty()) { p_node=_open.top(); _open.pop(); int x=p_node->_x; int y=p_node->_y; _seal[x][y].flag=SEAL; for(int i=0;i<4;++i) { int tx=x+direc[i][0]; int ty=y+direc[i][1]; if(bound(tx,ty)==false||_maze[tx][ty]=='1'||_seal[tx][ty].flag==SEAL)//边界,无路,封闭,则不考虑这个格 { continue; } if(_seal[tx][ty].flag==UNVISITED) { if(tx==_ex&&ty==_ey) { print(p_node); cout<<"("<<tx<<","<<ty<<")"<<endl; cout<<"总共走了:"<<p_node->_F<<"步"<<endl; return; } Queue_Node *temp=new Queue_Node; _seal[tx][ty].flag=OPEN; _seal[tx][ty].point=temp; temp->pre=p_node; temp->_G=p_node->_G+1; temp->_x=tx; temp->_y=ty; temp->_H=get_H(tx,ty); temp->_F=temp->_G+temp->_H; _open.push(temp); } else { Queue_Node *temp=_seal[tx][ty].point; if(p_node->_G+1<temp->_G) { temp->_G=p_node->_G+1; temp->pre=p_node; temp->_F=temp->_G+temp->_H; } } } } cout<<"没有从("<<_sx<<","<<_sy<<")--->"<<"("<<_ex<<","<<_ey<<")的路径"<<endl; } //打印路径 void print(Queue_Node *p) { if(p==NULL) { return; } print(p->pre); cout<<"("<<p->_x<<","<<p->_y<<"),"; } //边界判断 bool bound(int x,int y) { return (x<=_len)&&(x>=1)&&(y<=_wid)&&(y>=1); } //计算曼哈顿距离H函数 int get_H(int x,int y) { return ab(x-_ex)+ab(y-_ey); } //绝对值函数 int ab(int i) { return i<0? -i:i; } private: //仿比较函数结构体 struct cmp { bool operator()(Queue_Node *n1,Queue_Node *n2) { return n1->_F>n2->_F; } }; priority_queue<Queue_Node *,vector<Queue_Node *>,cmp> _open;//最小堆(开放列表) int _len,_wid;//迷宫左边长,上边宽 int _sx,_sy,_ex,_ey; Seal **_seal;//动态开辟封闭列表 unsigned char **_maze;//迷宫地图 }; int main() { A_Star test; return 0; }
A*算法总结
1. 将开始节点放入开放列表(开始节点的F和G值都视为0);
2. 重复一下步骤:
i. 在开放列表中查找具有最小F值的节点,并把查找到的节点作为当前节点;
ii. 把当前节点从开放列表删除, 加入到封闭列表;
iii. 对当前节点相邻的每一个节点依次执行以下步骤:
1. 如果该相邻节点不可通行或者该相邻节点已经在封闭列表中,则什么操作也不执行,继续检验下一个节点;
2. 如果该相邻节点不在开放列表中,则将该节点添加到开放列表中, 并将该相邻节点的父节点设为当前节点,同时保存该相邻节点的G和F值;
3. 如果该相邻节点在开放列表中, 则判断若经由当前节点到达该相邻节点的G值是否小于原来保存的G值,若小于,则将该相邻节点的父节点设为当前节点,并重新设置该相邻节点的G和F值.
iv. 循环结束条件:
当终点节点被加入到开放列表作为待检验节点时, 表示路径被找到,此时应终止循环;
或者当开放列表为空,表明已无可以添加的新节点,而已检验的节点中没有终点节点则意味着路径无法被找到,此时也结束循环;
3. 从终点节点开始沿父节点遍历, 并保存整个遍历到的节点坐标,遍历所得的节点就是最后得到的路径;
路径排序
决定哪些方格会形成路径的关键是下面这个等式:
F = G + H
这里
· G=从起点A沿着已生成的路径到一个给定方格的移动开销。
· H
=从给定方格到目的方格的估计移动开销。这种方式常叫做试探,有点困惑人吧。其实之所以叫做试探法是因为这只是一个猜测。在找到路径之前我们实际上并不知
道实际的距离,因为任何东西都有可能出现在半路上(墙啊,水啊什么的)。本文中给出了一种计算H值的方法,网上还有很多其他文章介绍的不同方法。
我们要的路径是通过反复遍历开放列表并选择具有最小F值的方格来生成的.
重要的是,在计算H值时并不考虑任何障碍物。因为这是对剩余距离的估计值而不是实际值(通常是要保证估计值不大于实际值――译者注)。这就是为什么这个方式被叫做试探
法的原因了。
主要在于维护开放封闭列表的方式,这里用一个结构体矩阵_seal[][]标记开放或者封闭,如果开放,则point还记录了该格子所对应状态结点的指针,可以方便直接取出该节点修改信息.