K短路:
在图论中,K短路问题是指在一个图中找到从起点s到终点t的第K短的路径。其中,第1短路径即为最短路径。K短路算法在实际应用中有着广泛的用途,如在通信网络中找到替代的最短路径等。
基本概念
- K短路:从起点s到终点t的第K短的路径。当K=1时,即为最短路径。
- Dijkstra算法:一种用于求解单源最短路径的贪心算法,但只能找到一条最短路径。
- A*算法:一种启发式搜索算法,通过估价函数f(n) = g(n) + h(n)来估计从起点到终点的路径长度,其中g(n)是从起点到当前节点的实际代价,h(n)是从当前节点到终点的最佳路径估计代价。
K短路算法的实现
1. 基于Dijkstra和A*的K短路算法
K短路算法的基本思想是在每次迭代中,找到从起点到终点的第K短路径。通常,我们结合Dijkstra算法和A*算法来实现这一目标。
步骤:
- 预处理:使用Dijkstra算法从终点t反向计算图中每个节点到t的最短路径长度,存储在数组dis[]中。这个步骤是为了计算A*算法中的h(n)。
- 初始化:使用优先队列(最小堆)来存储待扩展的路径,队列中的元素包括当前路径的估价f(n)、实际代价g(n)和当前节点v。
- 迭代过程:
- 从队列中取出估价最小的节点now。
- 如果now是终点t,并且这是第k次到达t,则返回当前路径的总代价。
- 如果now到达t的次数超过k,则跳过此节点。
- 否则,扩展now节点,生成所有可能的下一节点,并计算其估价f(next) = g(now) + w(now, next) + dis[next],其中w(now, next)是now到next的边的权重。
- 将新的节点加入队列中。
- 结束条件:如果队列为空,表示没有找到第k条路径,返回-1或其他错误标识。
推荐相关视频学习链接:【B27 A*算法 第K短路】https://www.bilibili.com/video/BV19k4y1K7gV?vd_source=4c9eb38d8205116069b961c84f64c958
接下来看一下相关代码(此代码维护二元组)
#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
using namespace std;
typedef pair<int,int> pii;
const int N = 1e3 + 10, M = 1e5 + 10;
int n, m, S, T, K;
int dist[N], st[N], cnt[N];
vector<pii> e[N], ne[N]; //e数组是用来存从终点T到其余各点的边,实际上是没有这些边的,只是将原来的单向边进行反向,就可以表示出从T到其余各点的边
struct node{
int s, v, d;
bool operator<(const node &x) const{
return s > x.s;//如果你使用的是默认的比较函数std::less<T>(或者没有显式指定比较函数,因为std::less<T>是默认的),那么它会调用元素的operator<(如果已定义)。在这种情况下,operator<的bool返回值表示第一个元素是否“小于”第二个元素,而std::priority_queue会将这些“较小”的元素放在堆的底部(尽管它们仍然很容易被访问,但不是最先被移除的)。然而,由于std::priority_queue是一个最大堆,所以实际上被视为“最大”的元素(即那些operator<返回false的元素)会被放置在堆顶。
}
};
void dijkstra() {
memset(dist, 0x3f, sizeof dist);
dist[T] = 0;//这里求的是从T为起点的,到其余各点的最短距离
priority_queue<pii, vector<pii>, greater<pii>> q;
q.push({0, T});
while(q.size()) {
int ver = q.top().second, dis = q.top().first;
q.pop();
if(st[ver]) continue;
st[ver] = 1;
for(pii t : e[ver]) {
int u = t.first, w = t.second;
if(!st[u] && dist[ver] + w < dist[u]) {
dist[u] = dist[ver] + w;
q.push({dist[u], u});
}
}
}
}
int aStar() {
priority_queue<node> q;
q.push({dist[S], S, 0});
while(q.size()) {
int ver = q.top().v, dis = q.top().d;
q.pop();
cnt[ver]++;
if(cnt[T] == K) return dis;
for(pii t : ne[ver]) {
int u = t.first, w = t.second;
q.push({dist[u] + dis + w, u, dis + w});
}
}
return 0;
}
int main() {
cin >> n >> m;
for(int i = 1; i <= m; i++) {
int a, b, c; cin >> a >> b >> c;
ne[a].push_back({b, c});//这里建的是单向边
e[b].push_back({a, c});//反向建边,和原来边一样,同样是单向边,只不过反向
}
cin >> S >> T >> K;
if(S == T) K++; //这种情况对应有环,即可以自己到自己,第一次从自己出发,所以k++,去掉第一次
dijkstra();//先预处理从T到其余各点的最短距离,即预测数组
// for(int i = 1; i <= n; i++) {
// cout << "i: " << i << " dist[i]: " << dist[i] << '\n';
// }
cout << aStar();
return 0;
}