A*算法的介绍
游戏中的寻路通常由深度优先搜索(DFS)、广度优先搜索(BFS)或者A算法实现。其中DFS与BFS属于状态式搜索,A算法属于启发式搜索。今天给大家介绍一下A*算法。
-
G(n)、H(n)、F(n)
G(n)代表从起点到达节点n的实际代价值
H(n)代表从节点n到达终点的估计代价值
F(n)代表从起点经由节点n到达终点的估计代价值下面通过一个例子来计算 G(n)、H(n)、F(n)
绿色方块为起点,蓝色为障碍,红色代表终点
-
G(n)、H(n)、F(n)的计算(以起点右边F=40的方块为例)
G=当前格的G值 + 父节点(上一格)的G值
直接将起点的右边这个方块称为当前格了。
当前格的G值=10+0。
这个10是当前格与父节点之间的代价,如果当前格在上一格的正方向,则代价为10,如果在上一格的斜方向则代价为14,可以对比当前这一格和当前上方的一格。然后要用这个代价加上父节点的代价,由于父节点是起点,所以父节点的G=0。H=(当前格与终点之间x方向的单位差 + y方向的单位差)*10
当前格的H=(3+0)*10
当前格与终点在x方向上差了三个单位,y方向差了0个单位F=当前格的G值+H值
当前格F=10+30
3.开启列表与关闭列表
两个用来存储节点的容器,因为有很多的增删操作,所以选择List是比较好的。开启列表中存储的是可以行走的节点,关闭列表存储走过的节点,我们会在开启列表中选择一个F最小的值去走,那就要把这个点从开启列表中移除并加入关闭列表。
4.流程
1.将起点放入开启列表中
2.while(开启列表不为空)
{
1.在开启列表中找到F值最低的点,称为当前格
2.将当前格加入到关闭列表中,从开启列表移除
3.遍历当前格的周围8个点
if(不能走 || 在关闭列表中)
continue;
if(在开启列表中)
当前的G值比较原来的G值,如果小于,更新GF值,更新父节点为当前格
else
将点放入到开启列表中,计算GHF值,设置父节点为当前格
判断终点是否在关闭列表中,如果在,推导路径
}
代码实现
#include <vector>
#include <list>
const int kCost1 = 10; //直移一格消耗
const int kCost2 = 14; //斜移一格消耗
struct Point
{
int x, y; //点坐标,x代表横排,y代表竖列
int F, G, H; //F=G+H
Point* parent; //当前点的父节点,用于回溯查找路径
Point(int x, int y)
:x(x),
y(y),
F(0),
G(0),
H(0),
parent(NULL) //带参构造
{
}
};
//A*算法
class Astar
{
public:
//初始化A*算法使用的地图
void InitAstar(std::vector<std::vector<int>> &_maze);
//获取路径:1.开始点 2.结束点 3.斜边移动是否考虑四周有障碍物
std::list<Point*> GetPath(Point &startPoint, Point &endPoint, bool isIgnoreCorner);
private:
//查找路径的方法:A*算法的核心逻辑
Point *findPath(Point &startPoint, Point &endPoint, bool isIgnoreCorner);
//获取周围八个点,如果能走或者不在关闭列表中,则放入vector并返回
std::vector<Point *> getSurroundPoints(const Point *point, bool isIgnoreCorner) const;
//判断某个点是否可走
bool isCanreach(const Point *point, const Point *target, bool isIgnoreCorner) const;
//判断开启/关闭列表中是否包含某点
Point *isInList(const std::list<Point *> &list, const Point *point) const;
//从开启列表中查找F值最小的节点
Point *getLeastFpoint();
//计算FGH值
int calcG(Point *temp_start, Point *point);
int calcH(Point *point, Point *end);
int calcF(Point *point);
private:
std::vector<std::vector<int>> maze;//地图(maze:迷宫)
std::list<Point *> openList; //开启列表
std::list<Point *> closeList; //关闭列表
};
#include <math.h>
#include "Astar.h"
void Astar::InitAstar(std::vector<std::vector<int>> &_maze)
{
maze = _maze;
}
int Astar::calcG(Point *temp_start, Point *point)
{
//如果是斜边,extraG为14,否则为10
int extraG = (abs(point->x - temp_start->x) + abs(point->y - temp_start->y)) == 1 ? kCost1 : kCost2;
//如果是初始节点,则其父节点是空
int parentG = point->parent == NULL ? 0 : point->parent->G;
//当前点的G值 = 父节点的G值 + 额外的G值
return parentG + extraG;
}
int Astar::calcH(Point *point, Point *end)
{
//欧几里距离:两点之间的直线距离
//return sqrt((double)(end->x - point->x)*(double)(end->x - point->x) + (double)(end->y - point->y)*(double)(end->y - point->y))*kCost1;
//曼哈顿距离:水平距离+垂直距离
return (abs(end->x - point->x) + abs(end->y - point->y)) * kCost1;
}
int Astar::calcF(Point *point)
{
//F = G + H
return point->G + point->H;
}
Point* Astar::getLeastFpoint()
{
//将第一个点作为F值最小的点,进行比较,最终拿到F值最小的点
if (!openList.empty())
{
auto resPoint = openList.front();
for (auto &point : openList)
if (point->F<resPoint->F)
resPoint = point;
return resPoint;
}
return NULL;
}
Point* Astar::findPath(Point& startPoint, Point& endPoint, bool isIgnoreCorner)
{
//把起始点添加到开启列表
openList.push_back(new Point(startPoint.x, startPoint.y));
do
{
auto curPoint = getLeastFpoint();//找到F值最小的点
openList.remove(curPoint); //从开启列表中删除
closeList.push_back(curPoint);//放到关闭列表
//找到当前周围八个格中可以通过的格子(1.可走且没有在关闭列表)
std::vector<Point*> surroundPoints = getSurroundPoints(curPoint, isIgnoreCorner);
for (auto &target : surroundPoints)
{
//对某一个格子,如果它不在开启列表中,加入到开启列表,设置当前格为其父节点,计算F G H
if (!isInList(openList, target))
{
target->parent = curPoint;
target->G = calcG(curPoint, target);
target->H = calcH(target, &endPoint);
target->F = calcF(target);
openList.push_back(target);//添加到开启列表中
}
//对某一个格子,它在开启列表中,计算G值, 如果比原来的大, 就什么都不做,否则设置它的父节点为当前点,并更新G和F
else
{
int tempG = calcG(curPoint, target);
if (tempG < target->G)
{
target->parent = curPoint;
target->G = tempG;
target->F = calcF(target);
}
}
//查找结束点是否已经在开启列表中,如果在,这时候路径被找到。
Point *resPoint = isInList(openList, &endPoint);
//如果返回不为空,表示找到终点,则通过终点的parent一直反推得出路径
if (resPoint)
return resPoint;
}
}while (!openList.empty());//当开启列表为空时,跳出循环
return NULL;//找不到一条路径
}
std::list<Point*> Astar::GetPath(Point &startPoint, Point &endPoint, bool isIgnoreCorner)
{
Point *result = findPath(startPoint, endPoint, isIgnoreCorner);
std::list<Point *> path;
//返回路径,如果没找到路径,返回空链表
while (result)
{
path.push_front(result);
result = result->parent;//通过parent回溯
}
//清空临时开闭列表,防止重复执行GetPath导致结果异常
openList.clear();
closeList.clear();
return path;
}
Point* Astar::isInList(const std::list<Point *> &list, const Point *point) const
{
//判断某个节点是否在列表中,这里不能比较指针,因为每次加入列表是新开辟的节点,只能比较坐标
for (auto p : list)
if (p->x == point->x&&p->y == point->y)
return p;
return NULL;
}
bool Astar::isCanreach(const Point *point, const Point *target, bool isIgnoreCorner) const
{
//如果点与当前节点重合、超出地图、是障碍物、或者在关闭列表中,返回false
if (target->x<0 || target->x>maze.size() - 1
|| target->y<0 || target->y>maze[0].size() - 1
|| maze[target->x][target->y] == 1
|| (target->x == point->x&&target->y == point->y)
|| isInList(closeList, target))
{
return false;
}
else
{
if (abs(point->x - target->x) + abs(point->y - target->y) == 1)//非斜角可以
return true;
else
{
//斜对角要判断是否绊住
if (maze[point->x][target->y] == 0 && maze[target->x][point->y] == 0)
return true;
else
return isIgnoreCorner;
}
}
}
std::vector<Point *> Astar::getSurroundPoints(const Point *point, bool isIgnoreCorner) const
{
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++)
if (isCanreach(point, new Point(x, y), isIgnoreCorner))//判断是否能走,能走则加入到vector中返回
surroundPoints.push_back(new Point(x, y));
return surroundPoints;
}
排序算法的应用
在游戏中,排序算法一般都是用来做排名的,像下面这张图
在这么多种排序算法中,一般是选择归并排序和快速排序,这两种是较优的,一般归并排序运用在对文件的排序,而快速排序适合大多数情况。