A*算法

什么是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;
}

最后结果

  • 11
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值