Bootstrap

【数据结构与算法】A*算法——自动寻路

一.为什么用A*算法

上节课我们已经讲了最短路径算法,但是我们为什么还要使用A*算法呢?
那就要讲讲最短路径算法的缺点了.
很明显,我们要得到最短的,那么就需要比较,就需要我知道所有的路径,然后比较出最短的,那么我需要知道所有的路径这是非常庞杂的工作.

A*算法就巧妙的运用了人的思想,类似于人工智能一样,不需要事先知道所有的路径,只需要每一步都是朝向目的地的,同时附近可以走一步的.
虽然得出的未必是最短的一条路径,但是绝对是相对近的一条路.

二.A*算法的实现原理

那么我们需要注意的点就可以分为离终点有多远,附近的下一步能不能走通.
一般地图我们都可以当做二维数组来进行处理.
我们直接看图说话:
如果12是我们的起点,16是我们姚到的目的地,那我们是如何操作的呢?
看下面的步骤吧!
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
哎呀妈呀,可算是画完了.
这就是A*算法,接下来我们来代码实现吧.

三.A*算法的实现

头文件,我们先来看看需要做哪些事?

这是设置了从起点到改点的步数权重,就是G.
刚刚我是用的1,我们可以用10,都是一样的.
在这里插入图片描述
每个格子的属性,因为我们用的是二维数组表示地图,那么x,y代表数组下标位置.
G是起点格子到该格子的步数,H是该格子到终点格子的步数.
我们以计算总的步数F来判断下一步该走哪一个格子,也就是F最小的.
parent就是链接路径,
在这里插入图片描述
格子我们动态分配内存,同时最后我们需要清理.
当然要初始化地图和寻找路线.
在这里插入图片描述

1.初始化地图

二维数组的基本信息保存,其他函数需要用.
在这里插入图片描述
在这里插入图片描述

2.格子初始化

在这里插入图片描述

3.两个列表

在这里插入图片描述

4.起点到终点的路径

因为我们最后找到的节点是终点,但是我们需要的路径是从起点到终点,所以我们需要正序的将格子放入list容器中.
在这里插入图片描述

5.起点到终点的最佳路径★

首先将起点加入到openList.
在这里插入图片描述
如果openList不为空就一直找终点.最后为空就说明没有找到.
在这里插入图片描述
下面的都是在上面这个循环里面的.

先找到openList里面最小的F的格子.
在这里插入图片描述
对容器的遍历用迭代器
在这里插入图片描述
当这个格子判断后,我们就可以放入到closeList了,同时在openList里面排除.
在这里插入图片描述

然后我们就可以判断这个节点周围的点是否可以走通.
在这里插入图片描述
用vector容器来装可以走通的格子.
因为我们需要的这个格子周围的点,我们可以用两个for循环就包含了9个格子,但是下面我们会根据条件排除一些点.

在这里插入图片描述
如果越界了不可以,因为是一个二维数组,如果是障碍物不可以,这里我们在二维数组中设置的值为1的是障碍物.,如果在closeList的也不可以,因为我们已经走过了.
同时我们只需要上下左右就可以了.
在这里插入图片描述
判断某个格子是否在某个列表.
在这里插入图片描述
这个时候我们就得到了周围可走通格子的容器了,我们进行遍历计算其F值.

在这里插入图片描述
对于周围每个可以走通的格子我们进行判断是否在openList里面,如果没有就加入,有的话,就判断需不需要进行更新.判断的依据就是G是否更小,因为H都是一样的.

在这里插入图片描述
距离起点的步数计算.就是当前各个到下一步的步数+其父亲节点的G步数.
在这里插入图片描述
H(点到终点的距离)计算用了勾股定理.
如2~16的距离.
在这里插入图片描述

在这里插入图片描述
F的计算自不必多说.
在这里插入图片描述

遍历完周围的格子后,就清空,下一次又来用,并判断终点的格子有没有加入到openList.
如果有,就说明找到了,直接返回这个终点.
如果最后openList都为空了,那么就是没有找到终点,返回空.
在这里插入图片描述

6.资源的释放

将两个列表的格子释放内存.
在这里插入图片描述

四.完整代码

1.Astar.h

#pragma once
#include <list>

const int kCost1 = 10;
const int kCost2 = 14;

typedef struct _Point
{
	int x,y;//二维数组下标
	int F, G, H;//F=G+H
	struct _Point* parent;//parent的坐标
}Point;

//分配一个节点(格子)
Point* AllocPoint(int x, int y);

//初始化地图
void initAstarMaze(int* _maze, int _lines, int _colims);

//通过A*算法寻找路径
std::list<Point*>GetPath(Point* statPoint, Point* endPoint);

//清理资源,结束后必须调用
void ClearAstarMaze();

2.Astar.cpp

#include <math.h>
#include "Astar.h"
#include <iostream>
#include <vector>

static int* maze;//迷宫对应的二维数组,使用一级指针表示
static int cols;//二维数组对于的列数
static int lines;//二维数组对于的行数

static std::list<Point*>openList;//开放列表
static std::list<Point*>closeList;//关闭列表

//搜索从起点到终点的最佳路径
static Point* findPath(Point* startPoint, Point* endPoint);

//从开启列表中返回F值最小的节点
static Point* getLeasFpoint();

//获取当前点周围可达的节点
static std::vector<Point*>getSurroundPoints(const Point* point);

//判断某点是否可以用于下一步判断
static bool isCanreach(const Point* point, const Point* target);

//判断开放/关闭列表中是否包含某点
static Point* isInList(const std::list<Point*>& list, const Point* point);

//计算FGH值
static int calcG(Point* temp_start, Point* point);
static int calcH(Point* point, Point* end);
static int calcF(Point* point);

//分配一个节点(格子)
Point* AllocPoint(int x, int y)
{
	Point* temp = new Point;
	memset(temp, 0, sizeof(Point));//初始值清零
	temp->x = x;
	temp->y = y;
	return temp;
}


//初始化地图
void initAstarMaze(int* _maze, int _lines, int _colums)
{
	maze = _maze;
	lines = _lines;
	cols = _colums;
}

//通过A*算法寻找路径
std::list<Point*>GetPath(Point* startPoint, Point* endPoint)
{
	Point* result = findPath(startPoint, endPoint);

	std::list<Point*>path;
	//返回路径,如果没有找到路径,返回空链表
	while (result)
	{
		path.push_front(result);
		result = result->parent;
	}

	return path;
}


//搜索从起点到终点的最佳路径
static Point* findPath(Point* startPoint, Point* endPoint)
{
	openList.push_back(AllocPoint(startPoint->x,startPoint->y));
	while (!openList.empty())
	{
		//第一步,从开放列表中取最小的值
		Point* curPoint = getLeasFpoint();//找到F值最小的点

		//第二步,把当前节点放到关闭列表中
		openList.remove(curPoint);
		closeList.push_back(curPoint);

		//第三步,找到当前节点周围可达的节点
		std::vector<Point*>surroundPoints = getSurroundPoints(curPoint);
		std::vector<Point*>::const_iterator iter;
		for (iter = surroundPoints.begin(); iter != surroundPoints.end(); iter++)
		{
			Point* target = *iter;
			//对某个格子,如果不在开放列表中,就加入,设置当前格为其父节点.
			Point* exist = isInList(openList, target);
			if (!exist)
			{
				target->parent = curPoint;

				target->G = calcG(curPoint, target);
				target->H = calcH(target, endPoint);
				target->F = calcF(target);

				openList.push_back(target); 
			}
			else
			{
				int tempG = calcG(curPoint, target);
				if (tempG < target->G)
				{
					exist->parent = curPoint;
					exist->G = tempG;
					exist->F = calcF(target);
				}
				delete target;//如果已经存在了就不用了释放.
			}
		}

		surroundPoints.clear();
		Point* resPoint = isInList(openList, endPoint);
		if (resPoint)
		{
			return resPoint;
		}
	}
	return NULL;
}


//从开启列表中返回F值最小的节点
static Point* getLeasFpoint()
{
	if (!openList.empty())
	{
		Point* resPoint = openList.front();
		std::list<Point*>::const_iterator itor;
		for (itor = openList.begin(); itor != openList.end(); itor++)
		{
			if ((*itor)->F < resPoint->F)
			{
				resPoint = *itor;
			}
		}
		return resPoint;
	}
	return NULL;
}

//获取当前点周围可达的节点
static std::vector<Point*>getSurroundPoints(const Point* point)
{
	std::vector<Point*>surroundPoints;

	for (int x = point->x - 1; x <= point->x + 1; x++)
	{
		for (int y = point->y - 1; y <= point->y + 1; y++)
		{
			Point* temp = AllocPoint(x, y);
			if (isCanreach(point, temp))
			{
				surroundPoints.push_back(temp);
			}
			else
			{
				delete temp;
			}
		}
	}
	return surroundPoints;	
}

static bool isCanreach(const Point* point, const Point* target)
{
	if (target->x<0 || target->x>(lines - 1)
		|| target->y<0 || target->y>(cols - 1)
		|| maze[target->x * cols + target->y] == 1
		|| (target->x == point->x && target->y == point->y)
		|| isInList(closeList, target))
	{
		return false;
	}

	if (abs(point->x - target->x) + abs(point->y - target->y) == 1)//abs绝对值.上下左右
	{
		return true;
	}
	else
	{
		return false;
	}
		
}

//判断开启/关闭列表中是否包含某点
static Point* isInList(const std::list<Point*>& list, const Point* point)
{
	std::list<Point*>::const_iterator itor;
	for (itor = list.begin(); itor != list.end(); itor++)
	{
		if ((*itor)->x == point->x && (*itor)->y == point->y)
		{
			return *itor;
		}
	}
	return NULL;
}

static int calcG(Point* temp_start, Point* point)
{
	int extraG = (abs(point->x - temp_start->x) + abs(point->y - temp_start->y)) == 1 ? kCost1 : kCost2;
	int parentG = (point->parent == NULL ? NULL : point->parent->G);
	return extraG + parentG;
}
static int calcH(Point* point, Point* end)
{
	return (int)sqrt((double)(end->x - point->x) * (double)(end->x - point->x) + (double)(end->y - point->y) * (double)(end->y - point->y));
}
static int calcF(Point* point)
{
	return point->G + point->H;
}

//清理资源,结束后必须调用
void ClearAstarMaze()
{
	maze = NULL;
	lines = 0;
	cols = 0;
	std::list <Point*>::iterator itor;
	for (itor = openList.begin(); itor != openList.end();)
	{
		delete* itor;
		itor = openList.erase(itor);//获取到下一个节点
	}
	for (itor = closeList.begin(); itor != closeList.end();)
	{
		delete* itor;
		itor = closeList.erase(itor);//获取到下一个节点
	}
}

3.main.cpp

#include "Astar.h"
#include <list>
#include <iostream>
#include <Windows.h>

using namespace std;


int map[5][8] =
{
	{1,1,1,0,0,0,1,1},
	{0,0,0,0,1,0,0,0},
	{0,0,0,0,1,0,0,0},
	{0,0,0,0,1,0,0,0},
	{1,1,1,0,0,0,1,1},
};

void AStarTest()
{
	initAstarMaze(&map[0][0], 5, 8);
	//设置起始和结束点
	Point* start = AllocPoint(2, 1);
	Point* end = AllocPoint(2, 6);

	//通过A*算法寻找路径
	list<Point*>path=GetPath(start, end);

	cout << "寻路结果:" << endl;
	list<Point*>::const_iterator iter;
	for (iter = path.begin(); iter != path.end();iter++)
	{
		Point* cur = *iter;
		cout << '(' << cur->x << ',' << cur->y << ')' << endl;

		Sleep(800);
	}

	ClearAstarMaze();
}

int main()
{
	AStarTest();
	system("pause");
	return 0;
}

4.运行结果

在这里插入图片描述
就是走的这条路.
在这里插入图片描述

完结撒花
2024年8月16日17:29:59

;