A*——AcWing 178. 第K短路

A*

定义

A算法是一种广泛应用于路径搜索和图遍历的启发式搜索算法,它结合了最好优先搜索和Dijkstra算法的优点,旨在找到从初始节点到目标节点的最短路径。A算法在游戏AI、机器人导航、地图路线规划等领域有广泛应用。

A*算法的核心在于使用一个评估函数f(n)来确定下一个要探索的节点,该函数由两部分组成:

  1. 从起始节点到当前节点的实际代价 g(n)
  2. 从当前节点到目标节点的预估代价 h(n),这是通过启发式函数得到的,用于估计未来成本。

因此,对于每个节点n,A*算法计算的评估值为:f(n) = g(n) + h(n)

运用情况

  • 实时导航系统:如汽车导航、无人机路径规划,因为它能快速找到最优路径。
  • 游戏AI:例如角色的自动寻路,能够使NPC智能地选择通往目标的最短或最佳路径。
  • 机器人路径规划:帮助机器人在复杂环境中找到到达指定位置的最佳路径。
  • 图形用户界面中的自动布局:如自动布线算法中寻找最优布线方案。

注意事项

  1. 启发式函数的选择h(n)应该是一个乐观估计,即它不能过高估计实际成本,理想情况下应接近真实成本。常用的启发式函数有欧几里得距离、曼哈顿距离等。
  2. 空间与时间复杂度:A算法可能需要大量内存来存储已访问节点和待访问节点(优先队列),且在最坏情况下时间复杂度会较高。因此,优化数据结构(如使用A变种如IDA*或实现迭代深化)是必要的。
  3. 循环检测:在有向无环图中不会出现问题,但在更复杂的图中,需要额外机制避免无限循环。
  4. 终点可达性:确保目标节点是可达到的,否则算法将永远运行下去。

解题思路

  1. 初始化:设置起始节点的g(n)=0,其他所有节点的g(n)=∞,并将起始节点放入开放列表(优先队列,根据f(n)排序)。
  2. 循环:直到开放列表为空或找到目标节点。
    • 从开放列表中取出具有最小f(n)值的节点,称为当前节点。
    • 检查当前节点是否为目标节点,若是则结束,路径已找到。
    • 将当前节点移出开放列表并加入关闭列表,表示已访问过。
    • 遍历当前节点的所有邻居,对于每个未访问过的邻居:
      • 计算从起始点到该邻居的代价g(neighbor)
      • 计算启发式函数h(neighbor)
      • 更新邻居节点的总代价f(neighbor),如果新代价更低,则更新其父节点,并将其加入开放列表(如果不在其中)。
  3. 结束:若开放列表为空但未找到目标,说明没有路径;否则,通过回溯从目标到起始节点的父节点链,构建出最优路径。

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;
}

代码思路

  1. 数据结构定义:首先定义了用于表示边和节点对的pair类型,以及用于优先队列元素的三元组类型。同时定义了图的邻接表表示和相关变量。

  2. 图的构建:通过add函数向邻接表中添加边,这里维护了两个方向的边,一个在h[]中用于正向搜索(Dijkstra),另一个在rh[]中用于反向验证路径。

  3. Dijkstra算法:用于计算从目标点T到所有其他点的最短距离。这一步是预处理,为A*算法提供每一点到T的预估成本。

  4. A*搜索astar()函数实现了类似A*的搜索逻辑,尽管这里的启发式信息仅基于已经计算好的到T的最短距离,而非传统的启发式估计。它试图找到K条最短路径的总距离。通过记录每个节点被访问的次数cnt[]来控制找到K条路径的目标。

  5. 主函数逻辑:读取输入数据,构建图,执行Dijkstra预处理,然后调用astar()函数并输出结果。利用了优先队列(堆)来实现高效的节点选择。

改进思路

  1. 明确启发式函数:虽然本代码通过Dijkstra预先计算了距离作为启发式的一部分,但在标准A算法中,启发式通常还包括从当前节点到目标的直接估算。此代码实际上更接近于一种特殊目的的多路径查找而非典型的A。若要更贴近A*,可以考虑加入针对S到当前节点的实际启发式估算。

  2. 剪枝和优化:在astar()中,当找到K条路径后立即返回结果可以减少不必要的计算。此外,可以引入更高效的剪枝策略,比如当当前路径的预估总成本超过已找到的K条路径的成本时,停止对该路径的进一步扩展。

  3. 内存和效率:考虑到大型图的应用,可以考虑使用更高效的数据结构来管理优先队列,或者对已访问节点的标记和回溯过程进行优化,以减少内存占用和提高运行速度。

  4. 输入验证和错误处理:增加对输入数据的验证,确保图的有效性,以及处理可能的边界条件和异常情况,提高程序的健壮性。

  • 32
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

筱姌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值