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))。
正确性证明
证明将分为两部分:
- (dis(u,v) = dis'(u,v) - (h_u - h_v));
- 新图上没有负权边。
((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
", res);
return 0;
}