第K短路相关证明
Dijkstra正确性证明
采用反证法+数学归纳法
设目前已经出对的点得到的都是最短路,那么对于现在刚好出队这个点t来说:
- 因为它是优先队列的对头,而且图中的边权都是非负数,所以它不可能被队列中的点再更新
- 因为每个点只会出队一次,所以它也不会被已经出队的点再次更新
- 还没有入队的点距离更远,也不可能再更新它
综上,每个点的最短路距离在出队时就已经确定了。
A*实现的K短路正确性证明
首先,设每个点的估价总距离为h(x),实际总距离为a(x),当前求出的距离为s(x),估价函数为f(x)
1.
对于路径u1->u2->u3->...->un来说,如果我们正处在求k短路的迭代过程当中,那么其中的任何一个字段,都不可能包含i短路(i>k)的部分:
反证法:假设它包含,那么这一段就可以被替换为更短的子段,与我们的前提矛盾。
因此,对于每个点,当它的遍历次数达到k时,就可以无视它了
2.
对于满足拓扑序的一段路径,u1->u2->...->un来说,h(ui)非严格递增。
只有在最理想的情况下,对于j>i,才会有h(j)=h(i),其他情况下h(j)>h(i),所以h(j)>=h(i)
3.
最重要的一部分
重点第i次出队时,得到的就是第i短路。
可以采用归纳法证明:
第一次出队时,假设得到的这个dist1大于最优值dis1,那么
1.若当前优先队列中有最优路径上的点u,则dist1>dis1>=h(u),与当前点时优先队列队友矛盾。
2.若当前队列中没有最优路径上的点,只有已经出队的点中包含了最优路径上的点,那就需要先从当前队列点绕到那些已经出队的点,再把它入队来更新最优值,因为只有非负权边,显然会越找越大。
以此类推....
证毕。
代码如下:
#include <stdlib.h> #include <stdio.h> #include <algorithm> #include <cstring> #include <queue> #define x first #define y second using namespace std; typedef pair<int, int> PII; typedef pair<int, PII> PiI; const int N = 1005, M = 1e4 + 5; int n, s, t, m, k; int idx, h[N], ne[M << 1], val[M << 1], dist[N], e[M << 1]; int rh[N]; inline void add(int h[], int a,int b, int c) { ne[++idx] = h[a]; h[a] = idx; val[idx] = c; e[idx] = b; } bool vis[N]; inline void init(int s, int t) { priority_queue<PII, vector<PII>, greater<PII> > q; memset(dist, 0x3f, sizeof dist); q.push({0, s}); dist[s] = 0; while(!q.empty()) { auto t = q.top(); int dis = t.x, num = t.y; q.pop(); if(vis[num]) continue; vis[num] = 1; for(int i = rh[num]; i; i = ne[i]) { int j = e[i]; if(dist[j] > dis + val[i]) { dist[j] = dis + val[i]; q.push({dist[j], j}); } } } } int cnt[N]; int k_solve(int s, int t, int k) { priority_queue<PII, vector<PII>, greater<PII> > q; q.push({dist[s], s}); while(!q.empty()) { PII tmp = q.top(); q.pop(); int num = tmp.y; int ans = tmp.x - dist[num]; cnt[num]++; if(cnt[t] == k) return ans; if(cnt[num] > k) continue; for(int i = h[num]; i; i = ne[i]) { int j = e[i]; q.push({ans + val[i] + dist[j], j}); } } return -1; } int main() { scanf("%d%d", &n, &m); int a, b, c; for(int i = 1; i <= m; i++) { scanf("%d%d%d", &a, &b, &c); add(h, a, b, c); add(rh, b, a, c); } scanf("%d%d%d", &s, &t, &k); if(s == t) k++; init(t, s); #ifdef DEBUG printf("%d\n", dist[1]); #endif int ans = k_solve(s, t, k); printf("%d\n", ans); return 0; }
1.因为我们只需要终点的k短路,所以可以不用遇到每个点都存下来预估值,只需要用一个变量存储即可,在遇到终点且有解的情况下再输出即可。
2.对于终点来说,如果是第k次出队,就不需要再进行扩展,直接返回答案即可;对于其他点来说,因为是先将cnt++,再执行的后续操作,所以需要在cnt>k时才能退出,cnt==k时就退出会出错。