次短路和第 k 短路
次短路
在最短路的基础上,次短路可以由次短路\(~
+~\)边更新,也可以由最短路$~
+~边更新,这里注意一点,因为次短路更新时也会对其它次短路产生影响,所以更新次短路时也需要入队,我们先尝试更新最短路,成功的话就把原来的最短路给次短路,不成功的话就单独尝试更新次短路。
也就是说,我们可以在算法\(~dijkstra~\)的基础上,不断维护最短路和次短路。证明则十分简单,在下面的\(~k~\)短路算法证明时,可以带入次短路,具体实现见代码。
priority_queue<pair<int, int>, vector<vector<int, int> >, greater<pair<int ,int> > > q;
memset(dis, 0x3f, sizeof dis);
dis[1][1] = 0;//dis[i][1]表示 1 到 i 的最短路,dis[i][2]表示 1 到 i 的次短路
q.push({0, 1});
while(!q.empty())
{
int x = q.top().second;
int dist = q.top().first;
q.pop();
if(dist > dis[x][2]) continue;
for(auto it : v[x])
{
int cnt = it.first;
if(dis[cnt][1] > dist + it.second)//如果最短路需要松弛,那么当前的最短路将会被松弛,次短路就会松弛为原先的最短路
{
dis[cnt][2] = dis[cnt][1];
dis[cnt][1] = dist + it.second;
q.push({dis[cnt][1], cnt});
}
if(dis[cnt][2] > dist + it.second && dist + it.second > dis[cnt][1])//如果当前的路径比次短路优,而比最短路劣,那么只需要更新次短路
{
dis[cnt][2] = dist + it.second;
q.push({dis[cnt][2], cnt});
}
}
}
return dis[n][2];//返回
那么,关于次短路,应该就结束了。
k 短路
此时,如果按照次短路的做法显然是不可行的,这时候我们需要引入一个叫做启发式搜索的东西。
A* 算法
定义
A* 搜索算法(英文:A* search algorithm,A* 读作 A-star),简称 A* 算法,是一种在图形平面上,对于有多个节点的路径求出最低通过成本的算法。它属于图遍历(英文:Graph traversal)和最佳优先搜索算法(英文:Best-first search),亦是 BFS 的改进。
过程
定义起点 \(s\),终点 \(t\),从起点(初始状态)开始的距离函数 $g(x) $,到终点(最终状态)的距离函数 \(h(x\))$, \(h^{\ast}(x)1\),以及每个点的估价函数 \(f(x)=g(x)+h(x)\)。
A* 算法每次从优先队列中取出一个 \(f\) 最小的元素,然后更新相邻的状态。
如果 \(h\leq h*\),则 A* 算法能找到最优解。
上述条件下,如果 \(h\) 满足三角形不等式,则 A* 算法不会将重复结点加入队列。
当 \(h=0\) 时,A* 算法变为 \(Dijkstra\);当 \(h=0\) 并且边权为 \(1\) 时变为 BFS。
如何用 A* 解决 k 短路问题
A* 算法定义了一个对当前状态 \(x\) 的估价函数 \(f(x)=g(x)+h(x)\),其中 \(g(x)\) 为从初始状态到达当前状态的实际代价,\(h(x)\) 为从当前状态到达目标状态的最佳路径的估计代价。每次取出 \(f(x)\) 最优的状态 \(x\),扩展其所有子状态,可以用 优先队列 来维护这个值。
在求解 \(k\) 短路问题时,令 \(h(x)\) 为从当前结点到达终点 \(t\) 的最短路径长度。可以通过在反向图上对结点 \(t\) 跑单源最短路预处理出对每个结点的这个值。
由于设计的距离函数和估价函数,对于每个状态需要记录两个值,为当前到达的结点 \(x\) 和已经走过的距离 \(g(x)\),将这种状态记为 \((x,g(x))\)。
开始我们将初始状态 \((s,0)\) 加入优先队列。每次我们取出估价函数 \(f(x)=g(x)+h(x)\) 最小的一个状态,枚举该状态到达的结点 \(x\) 的所有出边,将对应的子状态加入优先队列。当我们访问到一个结点第 \(k\) 次时,对应的状态的 \(g(x)\) 就是从 \(x\) 到该结点的第 \(k\) 短路。
优化:由于只需要求出从初始结点到目标结点的第 \(k\) 短路,所以已经取出的状态到达一个结点的次数大于 \(k\) 次时,可以不扩展其子状态。因为之前 \(k\) 次已经形成了 \(k\) 条合法路径,当前状态不会影响到最后的答案。
当图的形态是一个 \(n\) 元环的时候,该算法最坏是 \(O(nk\log n)\) 的。但是这种算法可以在相同的复杂度内求出从起始点 \(s\) 到每个结点的前 \(k\) 短路。
具体实现
因为步骤较为繁琐,所以直接给出完整代码
#include <bits/stdc++.h>
#define IOS ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);
#define x first
#define y second
using namespace std;
typedef long long ll;
typedef pair<int, pair<int, int> > PIII;
typedef pair<int, int> PII;
int s, t, k, n, m, u, v, z;
struct node
{
int v;
int len;
};
vector<node> mp[1010], mp_dij[1010];
int dist[1010];
bool vis[1010];
int cnt[1010];
void dij()
{
priority_queue<PII, vector<PII>, greater<PII> > q;
q.push({0, t});
memset(dist, 0x3f, sizeof dist);
dist[t] = 0;
while(q.size())
{
int u = q.top().y;
q.pop();
if(vis[u]) continue;
vis[u] = true;
for(auto it : mp_dij[u])
{
int v = it.v;
if(dist[v] > dist[u] + it.len) dist[v] = dist[u] + it.len, q.push({dist[v], v});
}
}
return;
}
int A_star()
{
priority_queue<PIII, vector<PIII>, greater<PIII> > q;
q.push({dist[s], {0, s}});
while(q.size())
{
PIII cur = q.top();
q.pop();
int u = cur.y.y;
int dis = cur.y.x;
cnt[u] ++;
if(cnt[t] == k) return dis;
if(cnt[u] > k) continue;
for(auto it : mp[u])
{
int v = it.v;
q.push({dis + it.len + dist[v], {dis + it.len, v}});
}
}
return -1;
}
int main()
{
IOS;
cin >> n >> m;
for(int i = 0; i < m; i ++) cin >> u >> v >> z, mp_dij[v].push_back({u, z}), mp[u].push_back({v, z});
cin >> s >> t >> k;
if(s == t) k ++;
dij();
cout << A_star();
return 0;
}
标签:结点,dist,int,短路,cnt,dis
From: https://www.cnblogs.com/Liu-Tao-Chang/p/18350853