简介

Johnson算法是求解多源最短路的算法之一,核心操作是re-weight,适用于不包含负环(负权回路)的图。时间复杂度$O(nm+nmlogm)$。

Johnson算法

全源最短路常用方法是Floyd算法,时间复杂度$O(n^{3})$。当然我们也可以对每个顶点跑单源最短路。如果对每个顶点求一次Bellman-Ford算法,时间复杂度是$O(n^{2}m)$,似乎不如Floyd算法。如果是对每个顶点求一次Dijkstra算法,时间复杂度是$O(nm+nmlogm)$,看起来优于Floyd算法了。但是如何去除负边权的影响呢?

考虑一下直接把所有边权加个正数,但是显然不满足原图性质了:

直接加

这里提出一个re-weight操作,首先加上一个源点,并使其与所有顶点连通,新边权赋值为0,如下图:

加源

然后以新加顶点为源,跑Bellman-Ford算法,求出到其它顶点的最短路:

最短路

记录下最短路径长度h[] = {0, -3, 0, 0},删除刚刚新增加的结点。然后对原图边权进行处理,使用以下方法更新边权:$w’(u, v) = w(u, v) + (h[u] - h[v])$。

处理

这时,负边权的影响就被消除了,可以愉快的使用Dijkstra算法了。(懒一下,正确性请自行证明)。

模板

  1const int inf = 0x3f3f3f3f;
  2const int mxn = 1e3 + 5;
  3
  4struct E {
  5    int to, next, w;
  6} e[mxn];
  7
  8int H[mxn], tot;
  9
 10void add(int from, int to, int w) {
 11    e[tot] = {to, H[from], w};
 12    H[from] = tot++;
 13}
 14
 15void graph_init(int n)
 16{
 17    for(int i=1; i<=n; i++)
 18        H[i] = -1;
 19    tot = 0;
 20}
 21
 22int h[mxn], num[mxn];
 23bool vis[mxn];
 24queue<int> q;
 25
 26void spfa_init(int n)
 27{
 28    for(int i=1; i<=n; i++){
 29        h[i] = inf;
 30        num[i] = 0;
 31    }
 32}
 33
 34int spfa(int s, int n)
 35{
 36    h[s] = 0; q.push(s);
 37    num[s] = vis[s] = 1;
 38    while(!q.empty())
 39    {
 40        int u = q.front(); q.pop(); vis[u] = 0;
 41        for(int i=H[u]; ~i; i=e[i].next){
 42            int v = e[i].to, w = e[i].w;
 43            if(h[u]+w < h[v]){
 44                h[v] = h[u]+w;
 45                if(!vis[v]){
 46                    q.push(v), vis[v] = 1;
 47                    if(++num[v] > n) return -1;
 48                }
 49            }
 50        }
 51    }
 52    return 0;
 53}
 54
 55int dis[mxn];
 56priority_queue<pair<int, int>, vector<pair<int, int> >, greater<pair<int, int> > > pq;
 57
 58void dijkstra_init(int n)
 59{
 60    for(int i=1; i<=n; i++){
 61        vis[i] = 0;
 62        dis[i] = inf;
 63    }
 64}
 65
 66void dijkstra(int s, int n)
 67{
 68    pq.push({dis[s]=0, s});
 69    while(!pq.empty()){
 70        int u = pq.top().second; pq.pop();
 71        if(vis[u]) continue;
 72        vis[u] = 1;
 73        for(int i=H[u]; ~i; i=e[i].next){
 74            int v=e[i].to, w=e[i].w;
 75            if(!vis[v] && dis[u]+w < dis[v]){
 76                dis[v] = dis[u]+w;
 77                pq.push({dis[v], v});
 78            }
 79        }
 80    }
 81}
 82
 83int DIS[mxn][mxn];
 84
 85int main()
 86{
 87    int n, m;
 88    scanf("%d %d", &n, &m);
 89    graph_init(n);
 90
 91    for(int i=1; i<=m; i++)
 92    {
 93        int u, v, w;
 94        scanf("%d %d %d", &u, &v, &w);
 95        add(u, v, w);
 96    }
 97
 98    for(int i=1; i<=n; i++)
 99        add(0, i, 0);
100
101    spfa_init(n);
102    if(spfa(0, n) == -1)
103    {
104        printf("包含负权回路\n");
105    }
106    else
107    {
108        for(int u=1; u<=n; u++){
109            for(int i=H[u]; ~i; i=e[i].next){
110                e[i].w += h[u] - h[e[i].to];
111            }
112        }
113
114        for(int i=1; i<=n; i++){
115            dijkstra_init(n);
116            dijkstra(i, n);
117            for(int j=1; j<=n; j++){
118                DIS[i][j] = dis[j] - h[i] + h[j];
119                printf("%d ", DIS[i][j]);
120            }
121            printf("\n");
122        }
123    }
124
125    return 0;
126}
127
128
129/*
130Sample Input:
131
1324 5
1331 2 -3
1342 3 2
1351 4 3
1362 3 4
1373 4 1
138
139
140Sample Output:
141
1420 -3 -1 0
1431061109570 0 2 3
1441061109568 1061109565 0 1
1451061109567 1061109564 1061109566 0
146
147*/