第K短路相关证明

Dijkstra正确性证明

采用反证法+数学归纳法

设目前已经出对的点得到的都是最短路,那么对于现在刚好出队这个点t来说:

  1. 因为它是优先队列的对头,而且图中的边权都是非负数,所以它不可能被队列中的点再更新
  2. 因为每个点只会出队一次,所以它也不会被已经出队的点再次更新
  3. 还没有入队的点距离更远,也不可能再更新它

综上,每个点的最短路距离在出队时就已经确定了。

 

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时就退出会出错。

posted @ 2023-10-19 18:29  Gold_stein  阅读(4)  评论(0编辑  收藏  举报