Johnson 算法是用来解决在有负权重边图里的最短路径问题的,它主要了结合 Dijkstra 算法和 Bellman-Ford 算法。其实负数边的问题也可以用 Folyd 算法来解决,只不过它的算法复杂度是 ,而 Johnson 算法在稀疏图里复杂度是 ,会比 Folyd 好一点。
Reweight
我们考虑如下图结构
其中 0 -> 1
是负数的,是不能使用 Dijkstra 去求最短路径的。这时我们可能会想到把全部的边都加上 5 那大家不就都变成正数了?使用 Dijkstra 求完最短路径后再减回 5 那答案不就求到了么?这是一种思路,但是不完成正确,考虑如下图
这个图的 S 到 T 最短路径是 3(选上面那条路) ,但是所有边都加 1 后,最短路径就变成了 4 (选了下面那条路)。
而 Johnson 的方法是通过给每个节点设置一个值,用这些节点的值去做 reweight,公式如下:
就是节点 X 的值,这个值是通过 Bellman-Ford 求出来的。
h[x]
现在来说说怎么求这个 。其实很简单,在这个图中添加一个虚拟的节点,如下图所示,为什么说是虚拟的呢,因为用完就删除了。这个虚拟节点指向所有的节点,而指向所有节点的边权重为 0。
就是用 Bellman-ford 去求这个虚拟节点到每个节点的最短距离。以上图为例,每个节点的 h 值为:
节点 | h[x] |
---|---|
0 | 0 |
1 | -5 |
2 | 0 |
3 | 0 |
有了这些 值后就可以对每条边进行 reweight 操作了,最终的路径应该要减去开始节点的 h 值,再加上结束节点的 h 值。以 0 -> 1 -> 2
为例,Dijkstra 算法求出的路径是
然后再还原到真实的路径
以上面的图为例
Johnson 算法步骤
上面都是 Johnson 的关键步骤,下面就说说整个 Johnson 算法的流程。
- 添加虚拟节点到这个图里,并添加指向所有节点的虚拟边,这些边的权重为 0
- 以虚拟节点节点为起点,运行 Bellman-Ford 算法,求出到每个节点的最短距离,这些最短距离为每个节点的 值
- 用上面求出的 h 值去更新图里的边,使得
- 移除添加的虚拟节点和边
- 在每个节点运行 Dijkstra 计算到其它节点的最短距离
再来分析这个算法的时间复杂度,Bellman-Ford 算法时间复杂度是 ,Dijkstra 算法的时间复杂度是 ,因为每个节点都要做 Dijkstra,所以总的时间复杂度是 。