A*
定义
A算法是一种广泛应用于路径搜索和图遍历的启发式搜索算法,它结合了最好优先搜索和Dijkstra算法的优点,旨在找到从初始节点到目标节点的最短路径。A算法在游戏AI、机器人导航、地图路线规划等领域有广泛应用。
A*算法的核心在于使用一个评估函数f(n)
来确定下一个要探索的节点,该函数由两部分组成:
- 从起始节点到当前节点的实际代价
g(n)
。 - 从当前节点到目标节点的预估代价
h(n)
,这是通过启发式函数得到的,用于估计未来成本。
因此,对于每个节点n
,A*算法计算的评估值为:f(n) = g(n) + h(n)
。
运用情况
- 实时导航系统:如汽车导航、无人机路径规划,因为它能快速找到最优路径。
- 游戏AI:例如角色的自动寻路,能够使NPC智能地选择通往目标的最短或最佳路径。
- 机器人路径规划:帮助机器人在复杂环境中找到到达指定位置的最佳路径。
- 图形用户界面中的自动布局:如自动布线算法中寻找最优布线方案。
注意事项
- 启发式函数的选择:
h(n)
应该是一个乐观估计,即它不能过高估计实际成本,理想情况下应接近真实成本。常用的启发式函数有欧几里得距离、曼哈顿距离等。 - 空间与时间复杂度:A算法可能需要大量内存来存储已访问节点和待访问节点(优先队列),且在最坏情况下时间复杂度会较高。因此,优化数据结构(如使用A变种如IDA*或实现迭代深化)是必要的。
- 循环检测:在有向无环图中不会出现问题,但在更复杂的图中,需要额外机制避免无限循环。
- 终点可达性:确保目标节点是可达到的,否则算法将永远运行下去。
解题思路
- 初始化:设置起始节点的
g(n)=0
,其他所有节点的g(n)=∞
,并将起始节点放入开放列表(优先队列,根据f(n)
排序)。 - 循环:直到开放列表为空或找到目标节点。
- 从开放列表中取出具有最小
f(n)
值的节点,称为当前节点。 - 检查当前节点是否为目标节点,若是则结束,路径已找到。
- 将当前节点移出开放列表并加入关闭列表,表示已访问过。
- 遍历当前节点的所有邻居,对于每个未访问过的邻居:
- 计算从起始点到该邻居的代价
g(neighbor)
。 - 计算启发式函数
h(neighbor)
。 - 更新邻居节点的总代价
f(neighbor)
,如果新代价更低,则更新其父节点,并将其加入开放列表(如果不在其中)。
- 计算从起始点到该邻居的代价
- 从开放列表中取出具有最小
- 结束:若开放列表为空但未找到目标,说明没有路径;否则,通过回溯从目标到起始节点的父节点链,构建出最优路径。
AcWing 178. 第K短路
题目描述
AcWing 178. 第K短路 - AcWing
运行代码
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
typedef pair<int, PII> PIII;
const int N = 1010, M = 200010;
int n, m, S, T, K;
int h[N], rh[N], e[M], w[M], ne[M], idx;
int dist[N], cnt[N];
bool st[N];
void add(int h[], int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
void dijkstra()
{
priority_queue<PII, vector<PII>, greater<PII>> heap;
memset(dist, 0x3f, sizeof dist);
heap.push({0, T});
dist[T] = 0;
while(heap.size())
{
auto t = heap.top();
heap.pop();
int ver = t.y;
if(st[ver]) continue;
st[ver] = true;
for(int i = rh[ver]; ~i; i = ne[i])
{
int j = e[i];
if(dist[j] > dist[ver] + w[i])
{
dist[j] = dist[ver] + w[i];
heap.push({dist[j], j});
}
}
}
}
int astar()
{
priority_queue<PIII, vector<PIII>, greater<PIII>> heap;
heap.push({dist[S], {0, S}});
while(heap.size())
{
auto t = heap.top();
heap.pop();
int ver = t.y.y, distance = t.y.x;
cnt[ver] ++ ;
if(cnt[T] == K) return distance;
for(int i = h[ver]; ~i; i = ne[i])
{
int j = e[i];
if(cnt[j] < K) heap.push({dist[j] + distance + w[i], {distance + w[i], j}});
}
}
return -1;
}
int main()
{
cin >> n >> m;
memset(h, -1, sizeof h);
memset(rh, -1, sizeof rh);
for (int i = 0; i < m; i ++ )
{
int a, b, c;
cin >> a >> b >> c;
add(h, a, b, c);
add(rh, b, a, c);
}
cin >> S >> T >> K;
if (S == T) K ++ ;
dijkstra();
cout << astar() << endl;
return 0;
}
代码思路
-
数据结构定义:首先定义了用于表示边和节点对的pair类型,以及用于优先队列元素的三元组类型。同时定义了图的邻接表表示和相关变量。
-
图的构建:通过
add
函数向邻接表中添加边,这里维护了两个方向的边,一个在h[]
中用于正向搜索(Dijkstra),另一个在rh[]
中用于反向验证路径。 -
Dijkstra算法:用于计算从目标点T到所有其他点的最短距离。这一步是预处理,为A*算法提供每一点到T的预估成本。
-
A*搜索:
astar()
函数实现了类似A*的搜索逻辑,尽管这里的启发式信息仅基于已经计算好的到T的最短距离,而非传统的启发式估计。它试图找到K条最短路径的总距离。通过记录每个节点被访问的次数cnt[]
来控制找到K条路径的目标。 -
主函数逻辑:读取输入数据,构建图,执行Dijkstra预处理,然后调用
astar()
函数并输出结果。利用了优先队列(堆)来实现高效的节点选择。
改进思路
-
明确启发式函数:虽然本代码通过Dijkstra预先计算了距离作为启发式的一部分,但在标准A算法中,启发式通常还包括从当前节点到目标的直接估算。此代码实际上更接近于一种特殊目的的多路径查找而非典型的A。若要更贴近A*,可以考虑加入针对S到当前节点的实际启发式估算。
-
剪枝和优化:在
astar()
中,当找到K条路径后立即返回结果可以减少不必要的计算。此外,可以引入更高效的剪枝策略,比如当当前路径的预估总成本超过已找到的K条路径的成本时,停止对该路径的进一步扩展。 -
内存和效率:考虑到大型图的应用,可以考虑使用更高效的数据结构来管理优先队列,或者对已访问节点的标记和回溯过程进行优化,以减少内存占用和提高运行速度。
-
输入验证和错误处理:增加对输入数据的验证,确保图的有效性,以及处理可能的边界条件和异常情况,提高程序的健壮性。