游戏AI-寻路-A*寻路算法
算法介绍:
作用:在一个图中,提供一个起点A与一个终点B,给你找出一条估算出来较短的路
时间复杂度:n * log(m) ,n表示图中的节点数,m表示总边的数量
时间复杂度分析:
- 一般游戏中的图是一个二维矩阵,所以每个点的方向也就上下左右这么几个,所以每个点枚举方向的时间为常数
- 虽然复杂度为:n * log(m),但A*是启发式算法,其中大量的点可能都没被枚举到,真正的时间消耗远小于:n * log(m)
算法解析:
A*算法可以理解成是从BFS经历了两次优化,第一次优化成了Dijkstra算法,再从Dijkstra算法优化成了A*算法。
A*的算法流程:
- 初始化一个启发数组,array[p] = 点p到终点的距离(array的值要看程序员设计了,设计的好估算出来的较短路就越优秀)
- 准备一个优先队列,以期望费用值从小到大排列
- 将起点加入队列
- 取出队头
- 若节点为终点:结束
- 若节点已经过:跳过节点
- 若节点未经过:标记节点已经过,并记录费用
- 枚举队头节点的8个方向
- 将可达的新节点加入队列
- 节点的费用设置为:当前点的费用 + 当前点到新节点的费用
- 期望费用设置为:当前点的费用 + 当前点到新节点的费用 + 启发数组[新节点]
- 回到第4步循环
A*部分内容说明:
- 启发函数的作用:
- 这就是从Dijkstra到A*的优化,具体作用就是用启发函数去影响找点的优先级
- Dijkstra的逻辑:每次都能找到一个费用最低的点再用这个费用最低的点去维护出其他的费用最低的点出来,是一种贪心的思路,并且也可以证明以这种方式找出来的路一定是最短路
- A*的逻辑:我不仅要考虑我下一个检查的点是不是费用小的,我还要考虑它是不是离终点近,然后对点到终点的距离加权并影响检查的点的顺序。这种方式找最短路,离终点近的点其实并不一定费用会小,但很可能更快抵达终点
- 为什么用优先队列:
- 主要就是利用二叉堆加速查找期望费用最小的点是哪个
- 如何减小A*跑出来的最短路的误差:
- 降低期望数组的权重
- 设计更合理期望数组
算法实现:
#include<iostream>
#include<vector>
#include<queue>
using namespace std;
const int N = 25;
const int M = 100;
class Node {
public:
int X;
int Y;
int Cost;
int ExpectCost;
Node* FatherNode;
static int ExpectArray[N][M];
Node();
Node(int x, int y, Node* fatherNode) :X(x), Y(y), FatherNode(fatherNode)
{
if (fatherNode == nullptr)
{
Cost = 0;
ExpectCost = ExpectArray[x][y];
return;
}
Cost = fatherNode->Cost + 1;
ExpectCost = Cost + ExpectArray[x][y];
}
};
int Node::ExpectArray[N][M];
char MapInfo[N][M];
const int S_X = 0;
const int S_Y = 0;
const int E_X = N - 1;
const int E_Y = M - 1;
void CreateMap()
{
for (int i = 0; i < N; i++)
{
for (int j = 0; j < M; j++)
{
if (i == S_X && j == S_Y || i == E_X && j == E_Y)
{
MapInfo[i][j] = ' ';
}
else if (rand() % 5 == 0)
{
MapInfo[i][j] = '#';
}
else
{
MapInfo[i][j] = ' ';
}
}
}
}
void PrintMap()
{
for (int i = 0; i < N; i++)
{
for (int j = 0; j < M; j++)
{
cout << MapInfo[i][j];
}
cout << endl;
}
}
void InitExpectArray()
{
for (int i = 0; i < N; i++)
{
for (int j = 0; j < M; j++)
{
Node::ExpectArray[i][j] = abs(i - E_X) + abs(j - E_Y);
}
}
}
class Compare_Node_Pointer
{
public:
bool operator () (Node*& a, Node*& b) const
{
return a->Cost > b->Cost;
}
};
priority_queue<Node*, vector<Node*>, Compare_Node_Pointer>Queue;
void InitQueue()
{
while (!Queue.empty())
{
Queue.pop();
}
}
int DirectionX[4] = { 0,0,1,-1 };
int DirectionY[4] = { 1,-1,0,0 };
bool Flag[N][M];
void FindPath()
{
memset(Flag, false, sizeof(Flag));
vector<Node*>Path;
InitExpectArray();
InitQueue();
Node* StartNode = new Node(S_X, S_Y, nullptr);
Queue.push(StartNode);
while (!Queue.empty())
{
Node* CurNode = Queue.top();
Queue.pop();
if (CurNode->X == E_X && CurNode->Y == E_Y)
{
Path.push_back(CurNode);
break;
}
if (Flag[CurNode->X][CurNode->Y])
{
continue;
}
Flag[CurNode->X][CurNode->Y] = true;
for (int i = 0; i < 4; i++)
{
int NextX = CurNode->X + DirectionX[i];
int NextY = CurNode->Y + DirectionY[i];
if(0 <= NextX && NextX < N && 0 <= NextY && NextY < M && MapInfo[NextX][NextY] != '#')
{
Node* NextNode = new Node(NextX, NextY, CurNode);
Queue.push(NextNode);
if (NextX == CurNode->X && NextY == CurNode->Y)
{
printf("%d = %d + %d\n", NextX, CurNode->X, DirectionX[i]);
printf("%d = %d + %d\n", NextY, CurNode->Y, DirectionY[i]);
printf("\n");
}
}
}
}
if (Path.size() == 1)
{
Node* Node = Path[0];
while (true)
{
if (Node->FatherNode != nullptr)
{
printf("%d %d", Node->FatherNode->X, Node->FatherNode->Y);
Path.push_back(Node->FatherNode);
Node = Node->FatherNode;
}
else
{
break;
}
}
}
reverse(Path.begin(), Path.end());
for (int i = 0; i < Path.size(); i++)
{
printf("X = %d, Y = %d, Cost = %d\n", Path[i]->X, Path[i]->Y, Path[i]->Cost);
MapInfo[Path[i]->X][Path[i]->Y] = 'o';
}
}
int main()
{
CreateMap();
FindPath();
PrintMap();
system("pause");
return 0;
}