什么是A*算法?
A*算法是解空间的一种算法,一般用于地图中寻找最佳路径,比如游戏中NPC的移动等,属于分支定界法。
如何实现
首先要实现A*算法需要两个储存位置的链表,这里设为OPEN_list,CLOSE_list,其中设CLOSE_list储存已经走过的不会被考虑的节点,而OPEN_list则储存可以被考虑的节点,而对于每个节点(位置)都有6个成员,分别是代表位置的x,y,判断节点优先级的3个变量G,H,F,其中G表示当前节点与起点之间的距离,H表示当前节点与终点之间的距离,F则为G+H,用F判断下一个节点是否能走,还有就是指向上一个节点的指针parent,最后还有一个储存周围节点的数组data。
(下面的函数声明先不用管)
#define PARALLEL 10//节点平行
#define HYPOTENUSE 12//斜边
#define WIDE 8
#define HIGHT 10
//节点类
class Node
{
public:
Node()
{
x = y = G = F = H = 0;
this->parent = NULL;
}
Node(int x, int y)
{
this->x = x;
this->y = y;
this->parent = NULL;
}
int get_G(const Node&node)
{
//自身距离节点的距离加上父节点的G
int a = (abs(this->x - node.x) + abs(this->y - node.y) == 1) ? PARALLEL : HYPOTENUSE;
int b = this->parent == NULL ? 0 : this->parent->G;
return a + b;
}
int get_H(const Node&node)
{
return (int)sqrt((double)(this->x - node.x)*(double)(this->x - node.x) +
(double)(this->y - node.y)*(double)(this->y - node.y));
}
int get_F(){ this->F = this->G + this->H; return F; }
bool operator==(const Node&node)const
{
return this->x == node.x&&this->y == node.y;
}
int x, y;
//G表示由起点到指定的点移动的距离
//H表示由指定位置到终点所耗费的路程
//F=G+H
int G, F, H;
Node*parent;//指向上个节点的指针,方便寻找最终的路径
};
//地图类
class Map
{
public:
Map(){}
//设置起点终点
Map(Node*start, Node*end){ this->start = start; this->end = end; }
//返回最优路径
list<Node> get_best_path();
~Map();
//寻找路径,返回的是最后的节点
Node* find_close_path();
//将一个点附近的8个点放入Openlist
void putinto_node(Node*node);
//在Openlist中找到F最小的节点
Node* F_min_openlist();
//判断节点是否能加入数组
bool is_put_in_data(const Node&n1, const Node&n2);
//判断节点是否在Closelist中
bool is_in_list(const list<Node*>&l_temp,const Node&node);
//这两个链表里的指针都是new出来的对象
list<Node*>OPEN_list;
list<Node*>CLOSE_list;
vector<Node*>data;//保存一个节点附近8个节点
Node*start;//开始位置
Node*end;//结束位置
};
有了这些变量我们就可以开始实现A*算法了。
1、先把起点放入Openlist中
2、从Openlist中依次计算找出F值最小的节点
3、把该节点放入Closelist中
4、将该节点附近符合条件的节点放入data数组中
5、判断data中的元素是否在Openlist中,然后重新计算G,H,F值并放入Openlist中
6、重复2~5步直到Openlist中出现终点
然后我们开始详细实现:
先把起点放入Openlist中,再从Openlist中寻找F值最小的节点,这里只有起点,所以起点是F最小的节点,之后再把F最小的节点(起点)放入Closelist中。
之后从起点开始,检测周围的节点能不能走,这里我们需要判断周围的节点是否为边界,是否为墙体,是否和当前节点重合,并且还要保证周围的节点不能在Closelist的链表中(因为Closelist存放的是已经考虑过/走过的节点),如果不懂为什么不能在Closelist中请接着往下看。
//判断节点是否能加入数组,n1为n2周围的节点
bool Map::is_put_in_data(const Node&n1, const Node&n2)
{
if (n1.x<0 || n1.x>WIDE - 1
|| n1.y<0 || n1.y>HIGHT - 1
|| m_map[n1.x][n1.y] == 1
|| n1==n2
|| this->is_in_list(this->CLOSE_list, n1))
{
return false;
}
return true;
}
然后根据条件把周围的节点都放入数组data中,然后将数组data中的节点再判断是否已经在Openlist中,如果不在Openlist中就需要计算G,H,F值并放入Openlist中,并把该节点的parent指针指向上一个节点,如果该节点已经存在Openlist中那就需要重新计算G,H,F的值并且和原来的G,H,F比较,将较小的那个保存在Openlist中,注意改变G,H,F时记得parent指针也要改变指向
这是计算G,H,F的过程(在上面的Node类中已经实现):
这里计算G时需要提一嘴,如果有父节点的话,就直接将自身和父节点的距离和父节点的G相加即可,没有的话(起始节点周围)直接计算G就好
int get_G(const Node&node)
{
//自身距离节点的距离加上父节点的G
int a = (abs(this->x - node.x) + abs(this->y - node.y) == 1) ? PARALLEL : HYPOTENUSE;
int b = this->parent == NULL ? 0 : this->parent->G;
return a + b;
}
int get_H(const Node&node)
{
return (int)sqrt((double)(this->x - node.x)*(double)(this->x - node.x) +
(double)(this->y - node.y)*(double)(this->y - node.y));
}
int get_F(){ this->F = this->G + this->H; return F; }
这时Openlist中都是合法的数据了,然后我们再从Opnelist中寻找F最小的值作为下一步的起点,并将F值最小的节点放入Closelist。
然后记得要清空数组,然后判断终点是否在Openlist中,如果在的话那么终点的parent指针一定指向了上一个节点,所以结束循环就可以,当然循环的条件是判断Openlist是否为空
比如这张图,明显从起点开始只能不断往右走,每走一步就有一个节点放入Closelist中,直到最后Openlist中的节点都被转移到Closelist中,所以最后Openlist为空,不能找到最优路径。
如果Openlist中存在终点,那就已经找到最路径了,直接返回终点就好了,最后记得要把终点和起点倒过来才是从起点到终点的路径,找不到就继续重复以上的步骤直至找到或Openlist为空为止。
//寻找路径,返回的是最后的节点(主要函数)
Node* Map::find_close_path()
{
//先把开始点放入openlist
this->OPEN_list.emplace_front(this->start);
while (!this->OPEN_list.empty())
{
//在openlist中找到F值最小的元素
Node*F_min = this->F_min_openlist();
cout << "F_min(" << F_min->x << "," << F_min->y << ")" << endl;
//并将其从openlist移动到closelist
this->CLOSE_list.emplace_front(F_min);
this->OPEN_list.remove(F_min);
//之后将这个F最小的节点周围的点都加入data数组中
//等待进一步筛选进入Openlist
this->putinto_node(F_min);
//判断这些点是否在openlist中,不在直接计算F然后加入即可
//若在,就需判断当前F最小节点对当前节点的F值和之前parent指向节点的G值大小
//根据G值大小决定是否改变parent指向
//cout << "加入Openlist的" << endl;
for (vector<Node*>::iterator it = this->data.begin(); it != this->data.end(); it++)
{
//不在openlist
if (!this->is_in_list(this->OPEN_list, *(*it)))
{
(*it)->parent = F_min;
(*it)->G = (*it)->get_G(*F_min);
(*it)->H = (*it)->get_H(*this->end);
(*it)->get_F();
this->OPEN_list.emplace_front(*it);
//判断是否为最后的终点,如果是的话就需要修改终点的parent的指向
if (*(*it) == *this->end)
{
this->end = (*it);
}
/*cout << "(" << (*it)->x << "," << (*it)->y << ")" << "G:" << (*it)->G << "H:" << (*it)->H << endl;
cout << "(" << (*it)->x << "," << (*it)->y << ")" << "->";
cout << "(" << F_min->x << "," << F_min->y << ")" << endl;*/
}
else//存在于openlist
{
int num = (*it)->get_G(*this->start);
if (num < (*it)->G)
{
(*it)->G = num;
(*it)->get_F();
(*it)->parent = F_min;
}
delete (*it);
}
}
cout << "*********" << endl;
this->data.clear();
//结束条件,若终点在openlist中那么就结束循环
if (is_in_list(this->OPEN_list, *this->end))
{
return this->end;
}
Sleep(500);
}
return NULL;
}
//在Openlist中找到F最小的节点
Node* Map::F_min_openlist()
{
Node*min = this->OPEN_list.front();
for (list<Node*>::iterator it = this->OPEN_list.begin(); it != this->OPEN_list.end(); it++)
{
if (min->F > (*it)->F)
{
min = *it;
}
}
return min;
}
//将一个点附近的8个点放入Openlist
void Map::putinto_node(Node*node)
{
//cout << "加入数组的: " << endl;
for (int i = node->x - 1; i <= node->x + 1; i++)
{
for (int j = node->y - 1; j <= node->y + 1; j++)
{
Node temp(i, j);
if (this->is_put_in_data(temp,*node))
{
//cout << "F_min(" << temp.x << "," << temp.y << ")" << endl;
this->data.emplace_back(new Node(temp));
}
}
}
//cout << "---------------" << endl;
}
//判断节点是否在Openlist或Closelist中
bool Map::is_in_list(const list<Node*>&l_temp,const Node&node)
{
for (list<Node*>::const_iterator it = l_temp.begin(); it != l_temp.end(); it++)
{
if (node == *(*it))
return true;
}
return false;
}
最后把终点和起点倒过来
//返回最优路径
list<Node> Map::get_best_path()
{
//接受最后的节点
Node*end = this->find_close_path();
list<Node>temp;
while (end != NULL)
{
temp.emplace_front(*end);
//cout << end->x << "," << end->y << endl;
end = end->parent;
}
return temp;
}
最后不要忘记释放内存
Map::~Map()
{
if (!this->OPEN_list.empty())
{
for (list<Node*>::iterator it = this->OPEN_list.begin(); it != this->OPEN_list.end(); it++)
{
delete *it;
*it = NULL;
}
this->OPEN_list.clear();
}
if (!this->CLOSE_list.empty())
{
for (list<Node*>::iterator it = this->CLOSE_list.begin(); it != this->CLOSE_list.end(); it++)
{
delete *it;
*it = NULL;
}
this->CLOSE_list.clear();
}
}
测试的地图和代码
//墙表示1,空地表示0
int m_map[WIDE][HIGHT] =
{
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 0, 0, 0, 1, 1, 0, 1, 1, 1,
1, 0, 0, 0, 1, 1, 0, 0, 1, 1,
1, 0, 0, 0, 1, 1, 1, 0, 0, 1,
1, 1, 1, 0, 0, 1, 0, 0, 1, 1,
1, 1, 1, 1, 0, 0, 0, 0, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
};
int main()
{
Map temp;
temp.start = new Node(2, 2);
temp.end = new Node(2, 6);
list<Node>ll=temp.get_best_path();
if (!ll.empty())
{
cout << "路径为" << endl;
for (list<Node>::iterator it = ll.begin(); it != ll.end(); it++)
{
cout << "( " << it->x << "," << it->y << ")" << endl;
}
cout << "成功" << endl;
}
system("pause");
return 0;
}
最后结果