Johnson 和 Floyd 一样,是一种能求出无负环图上任意两点间最短路径的算法。该算法在 1977 年由 Donald B. Johnson 提出。
Part 1 算法概述
任意两点间的最短路可以通过枚举起点,跑
注意到堆优化的 Dijkstra 算法求单源最短路径的时间复杂度比 Bellman-Ford 更优,如果枚举起点,跑
priority_queue
实现,下同)的时间复杂度内解决本问题,比上述跑
但 Dijkstra 算法不能正确求解带负权边的最短路,因此我们需要对原图上的边进行预处理,确保所有边的边权均非负。
一种容易想到的方法是给所有边的边权同时加上一个正数
但这样的方法是错误的。考虑下图:
![c989c3f298d0af01524fae2767d0a0b6.png](https://i-blog.csdnimg.cn/blog_migrate/d07552396a8e6cba979f514f212d3995.jpeg)
但假如我们把每条边的边权加上
![6eb84bfa2dab72b2cb4e4c31fd5c21d5.png](https://i-blog.csdnimg.cn/blog_migrate/987500d297a64444ca76c6dac0f107c5.jpeg)
新图上
Johnson 算法则通过另外一种方法来给每条边重新标注边权。
我们新建一个虚拟节点(在这里我们就设它的编号为
接下来用 Bellman-Ford 算法求出从
假如存在一条从
接下来以每个点为起点,跑
容易看出,该算法的时间复杂度是
Q:那这么说,Dijkstra 也可以求出负权图(无负环)的单源最短路径了?
A:没错。但是预处理要跑一遍 Bellman-Ford,还不如直接用 Bellman-Ford 呢。
Part 2 正确性证明
为什么这样重新标注边权的方式是正确的呢?
在讨论这个问题之前,我们先讨论一个物理概念——势能。
诸如重力势能,电势能这样的势能都有一个特点,势能的变化量只和起点和终点的相对位置有关,而与起点到终点所走的路径无关。
势能还有一个特点,势能的绝对值往往取决于设置的零势能点,但无论将零势能点设置在哪里,两点间势能的差值是一定的。
接下来回到正题。
在重新标记后的图上,从
化简后得到:
无论我们从
为了方便,下面我们就把
上面的新图中
到这里我们的正确性证明已经解决了一半——我们证明了重新标注边权后图上的最短路径仍然是原来的最短路径。接下来我们需要证明新图中所有边的边权非负,因为在非负权图上,Dijkstra 算法能够保证得出正确的结果。
根据三角形不等式,新图上任意一边
至此,我们就证明了 Johnson 算法的正确性。
Part 3 参考代码
Reference
- Johnson's algorithm - Wikipedia
- 《算法导论(中译本,第 3 版)》,25.3 用于稀疏图的 Johnson 算法,409-411 页