Johnson 算法求解稀疏图中所有顶点对最短路径
题目描述
给你一个包含 n 个顶点和 m 条边的有向图,图中边的权重可能为负数,但图中不存在负权环。请你计算出图中所有顶点对之间的最短路径距离。即,对于每一对顶点 (i, j),输出从 i 到 j 的最短路径长度。如果 i 无法到达 j,则距离为无穷大。要求算法的时间复杂度在稀疏图(m = O(n))上优于直接对每个顶点运行 Dijkstra 算法(使用二叉堆时总复杂度 O(n² log n)),也优于 Floyd-Warshall 算法的 O(n³)。
解题思路
直接思路是:
- 对每个顶点运行 Dijkstra 算法(需非负权重),但图中有负权边,不能直接用。
- 用 Floyd-Warshall 算法(O(n³)),在稀疏图上不够高效。
Johnson 算法的核心思想是:通过引入一个“势能函数”对边权进行重新赋值,使得所有权重变为非负,但保持最短路径结构不变,然后对每个顶点运行一次 Dijkstra 算法。
关键步骤: - 增加一个虚拟源点,到所有顶点连一条权重为 0 的边,用 Bellman-Ford 算法计算从该源点到所有顶点的最短距离 h(v)(即势能)。
- 利用 h(v) 对原图每条边 (u, v) 的权重 w(u, v) 进行重新赋值:w'(u, v) = w(u, v) + h(u) - h(v)。可以证明 w'(u, v) ≥ 0。
- 对每个顶点 s,在权重 w' 的图上运行 Dijkstra 算法,得到距离 d'(s, v),再还原为原图上的最短距离 d(s, v) = d'(s, v) - h(s) + h(v)。
总复杂度:O(nm)(Bellman-Ford) + n × O(m log n)(n 次 Dijkstra,使用二叉堆) = O(nm log n),在稀疏图(m = O(n))上为 O(n² log n),优于 Floyd-Warshall 的 O(n³)。
逐步讲解
步骤1:理解问题与挑战
我们需要所有顶点对之间的最短路径。
- 如果权重都非负,可对每个顶点运行 Dijkstra(二叉堆实现),总时间 O(n(m + n) log n) ≈ O(n² log n)(当 m = O(n) 时)。
- 但如果有负权边,Dijkstra 不能直接使用。
- Floyd-Warshall 总是可以,但 O(n³) 在稀疏图上较慢。
Johnson 算法结合了 Bellman-Ford(处理负权)和 Dijkstra(高效求单源最短路),并通过重赋权消除负权。
步骤2:引入势能与重赋权原理
假设我们给每个顶点 v 分配一个势能 h(v)。对每条边 (u, v),定义新的权重:
\[w'(u, v) = w(u, v) + h(u) - h(v) \]
性质1:重赋权后,任意路径 p = (v₁, v₂, ..., vₖ) 的长度变化为:
原长度 w(p) = Σ w(vᵢ, vᵢ₊₁)
新长度 w'(p) = Σ [w(vᵢ, vᵢ₊₁) + h(vᵢ) - h(vᵢ₊₁)] = w(p) + h(v₁) - h(vₖ)
这意味着,路径长度只改变了起点和终点的势能差,因此任意两点间的最短路径在新权重下保持不变(因为所有从 u 到 v 的路径都增加了 h(u) - h(v))。
步骤3:使新权重非负的条件
我们希望 w'(u, v) ≥ 0 对所有边成立。
即 w(u, v) + h(u) - h(v) ≥ 0
整理得:h(v) ≤ h(u) + w(u, v)
这正是最短路径的三角不等式形式:如果 h(v) 是从某个源点 s 到 v 的最短距离,那么该不等式成立。
因此,如果我们能求出一组势能 h(v),使得对于每条边 (u, v),h(v) ≤ h(u) + w(u, v),则重赋权后所有边权非负。
这等价于以某个虚拟源点 s 为起点,到所有顶点的最短距离满足此性质。
步骤4:计算势能 h(v)
我们在原图的基础上增加一个新顶点 s,并从 s 向原图每个顶点 v 连一条权重为 0 的有向边。
然后以 s 为源点,运行 Bellman-Ford 算法(因为可能有负权边),求出 s 到每个顶点 v 的最短距离,记为 h(v)。
由于原图没有负权环,新图(加入 s 后)也没有负权环,所以 Bellman-Ford 能正确求出最短距离,并且满足:
对于原图中任意边 (u, v),有 h(v) ≤ h(u) + w(u, v)(因为 h(v) 是 s 到 v 的最短距离,h(u) + w(u, v) 是一条从 s 到 v 的路径,长度不会更短)。
因此,h(v) 就是我们需要的势能函数。
步骤5:重赋权与运行 Dijkstra
得到 h(v) 后,对原图的每条边 (u, v),计算新权重:
w'(u, v) = w(u, v) + h(u) - h(v)
由步骤4的结论,w'(u, v) ≥ 0。
然后,对原图的每个顶点 u,以 u 为源点,在权重 w' 的图上运行 Dijkstra 算法(因为 w' 非负),得到从 u 到各顶点 v 在新权重下的最短距离 D'[u][v]。
步骤6:还原为原图最短距离
由步骤2的公式:对于从 u 到 v 的任何路径,新长度 w'(p) = w(p) + h(u) - h(v)。
最短路径也满足此关系,因此:
原图最短距离 D[u][v] = D'[u][v] - h(u) + h(v)。
注意:如果 D'[u][v] 是无穷大(不可达),则 D[u][v] 也是无穷大。
步骤7:复杂度分析
- 加入虚拟源点,运行 Bellman-Ford:O(nm)。
- 计算新权重 w':O(m)。
- 运行 n 次 Dijkstra(二叉堆实现):O(n(m + n) log n) ≈ O(nm log n)(若 m ≥ n)。
总复杂度 O(nm log n),在稀疏图 m = O(n) 时,为 O(n² log n),优于 Floyd-Warshall 的 O(n³)。
步骤8:边界情况与实现细节
- 如果 Bellman-Ford 检测到负权环(虽然题目保证没有),则算法停止。
- 由于 h(v) 可能很大,在计算 w' 和还原 D 时可能溢出,需注意使用足够大的数据类型。
- 存储所有顶点对距离需要 O(n²) 空间。
- 实际实现时,不必显式构造新图,只需在 Dijkstra 中使用 w' 的计算函数即可。
总结
Johnson 算法巧妙地利用势能转换,将含负权但无负权环的图转换为非负权图,从而可应用高效的 Dijkstra 算法求所有顶点对最短路。它在稀疏图上比 Floyd-Warshall 更快,是解决此类问题的经典算法。