Johnson 算法求全源最短路学习笔记

Johnson 算法求全源最短路学习笔记

Johnson 和 Floyd 一样,是一种能求出无负环图上任意两点间最短路径的算法。该算法在 1977 年由 Donald B. Johnson 提出。

算法概述

任意两点最短路可以用 Floyd 算法解决,时间复杂度 \(O(n ^ 3)\)

在没有负权边时,可以跑 \(n\) 次 Dijkstra,时间复杂度 \(O(nm \log_2 n)\),在稀疏图上会比较优秀;

Johnson 算法通过一些方法使有负权边的图也适用于第二种做法。

详细步骤

先把所有点加入队列,用 spfa 进行松弛,每个点上的顶标记为 \(h_u\)

再将边权设为 \(w'(u,v) = w(u,v) + h_u - h_v\)

\(n\) 次 Dijkstra,此时新图上两点间的最短路 \(dis'(u,v)\) 和原图上最短路 \(dis(u,v)\) 满足 \(dis(u,v) = dis'(u,v) - (h_u - h_v)\)

时间复杂度 \(O(nm \log_2 n)\)

正确性证明

证明将分为两部分:

  1. \(dis(u,v) = dis'(u,v) - (h_u - h_v)\)
  2. 新图上没有负权边。

\((1)\)

\(p_1, p_k\) 间的一条路径为 \(p_1, p_2 , p_3 \dots , p_{k - 1}, p_k\)

则有:

\[\sum_{i = 1}^{k - 1} w'(p_i, p_{i + 1}) = \sum_{i = 1} ^ {k - 1} w(p_i, p_{i + 1}) + h_{p_i} - h_{p_{i + 1}} = h_{p_1} - h_{p_k} + \sum_{i = 1}^{k - 1} w(p_i, p_{i + 1}) \]

\((2)\)

\(h_u\) 顶标是用 spfa 松弛过的,所以满足三角形不等式:

对于一条边 \((u,v)\),一定有 \(h_u + w(u,v) \geq h_v\) ,即:\(w(u,v) + h_u - h_v \geq 0\)

\(Source\)

不一定是对的,没有例题,也没有拍过

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
int in() {
    int x = 0; char c = getchar(); bool f = 0;
    while (c < '0' || c > '9')
        f |= c == '-', c = getchar();
    while (c >= '0' && c <= '9')
        x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
    return f ? -x : x;
}
template<typename T>inline void chk_min(T &_, T __) { _ = _ < __ ? _ : __; }
template<typename T>inline void chk_max(T &_, T __) { _ = _ > __ ? _ : __; }

const int N = 1e3 + 5, mod = 998244353;

inline void add(int _, int __) {
    _ += __;
    if (_ >= mod)
        _ -= mod;
    if (_ < 0)
        _ += mod;
}

struct edge {
    int next, to, w;
} e[N << 1];
int ecnt = 1, head[N];
int n, m;
int h[N], dis[N][N];

inline void jb(const int u, const int v, const int w) {
    e[++ecnt] = (edge){head[u], v, w}, head[u] = ecnt;
}

void spfa() {
    static int vis[N];
    std::queue<int> q;
    for (int i = 1; i <= n; ++i)
        q.push(i), h[i] = 0, vis[i] = 1;
    while (!q.empty()) {
        int u = q.front(); q.pop();
        for (int i = head[u]; i; i = e[i].next) {
            int v = e[i].to;
            if (h[v] > h[u] + e[i].w) {
                h[v] = h[u] + e[i].w;
                if (!vis[v])
                    q.push(v), vis[v] = 1;
            }
        }
    }
}

void dijkstra(const int s, int *d) {
    static int vis[N];
    memset(vis, 0, sizeof(vis));
    std::priority_queue<std::pair<int, int> > q;
    memset(d, -1, sizeof(int) * (n + 1)); 
    q.push(std::make_pair(0, s));
    while (!q.empty()) {
        int u = q.top().second;
        q.pop();
        if (vis[u])
            continue;
        vis[u] = 1;
        for (int i = head[u]; i; i = e[i].next) {
            int v = e[i].to;
            if (d[v] > d[u] + e[i].w + h[u] - h[v]) {
                d[v] = d[u] + e[i].w + h[u] - h[v];
                q.push(std::make_pair(d[v], v));
            }
        }
    }
    for (int i = 0; i <= n; ++i)
        d[i] -= h[s] - h[i];
}

int main() {
    //freopen("in", "r", stdin);
    n = in(), m = in();
    while (m--) {
        int x = in(), y = in(), z = in();
        jb(x, y, z);
    }
    spfa();
    for (int i = 1; i <= n; ++i)
        dijkstra(i, dis[i]);
    int res = 0;
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= n; ++j)
            add(res, dis[i][j]);
    printf("%d\n", res);
    return 0;
}
posted @ 2019-11-03 15:57  15owzLy1  阅读(...)  评论(...编辑  收藏