Skip to content

单源最短路径:Dijkstra 算法

发布于  at 09:51 AM

计算机科学史伟大成就之一:Dijkstra 最短路径算法。这个算法适用于边的长度均不为负数的有向图,它计算从一个起始顶点到其它顶点的最短路径的长度。

单源最短路径问题

输入:有向图 G=(V,E)G=(V,E),起始顶点 sVs\in V,并且每条边 eEe \in E 的长度 lel_e 均为非负值。

输出:每个顶点 vVv\in Vdist(s,v)dist(s,v)

注意:dist(s,v)dist(s,v) 这种记法表示从 ssvv 的最短路径的长度(如果不存在从 ssvv 的路径,dist(s,v)dist(s,v) 就是 ++\infty)。

所谓路径的长度,就是组成这条路径的各条边的长度之和。

例如,如果一个图表示道路网,每条边的长度表示从一端到另一端的预期行车时间,那么单源最短路径问题就成为计算从一个起始顶点到 所有可能的目的地 的行车时间的问题。

边的长度可能为负的应用中,我们不应该使用 Dijkstra 算法,而应该使用比如 Bellman-Ford 算法。

Dijkstra 算法

Dijkstra 算法的整体结构和图的搜索算法相似。主循环每次迭代处理一个新的顶点。

算法的高级之处在于它采用了一种非常“聪明”的规则选择接下来处理哪个顶点:就是尚未处理的顶点中看上去 最靠近起始顶点 的那一个。

伪代码:

// 初始化
X := {s}
len(s) := 0, len(v) := +∞ 对于每个 v ≠ s
// 主循环
while 存在一条边(v, w), V ∈ X, w ∉ X, do
  (v*, w*) := 具有最小的 len(v) + lvw 的边
  把 w* 加入到 X 中
    len(w*) := len(v*) + lv*w*

集合 X 包含了这个算法已经处理过的顶点。一开始,X 只是包含了起始顶点 s(当然,len(s) =0)。这个集合 X 不断增长,直到它覆盖了从 s 到可以到达的所有顶点。

当这个算法把一个顶点添加到 X 时,它同时为这个顶点的 len 值赋一个有限的值。

主循环的每次迭代向 X 添加一个新顶点,即某条从 X 跨越到 V-X 的边 (v, w) 的头顶点。(如果不存在这样的边,算法就会终止,对于所有的 v ∉ X,都有 len(v)=+∞)。

符合条件的如果有多条,选着最低的 (v*, w*),它被定义为:

len(v)+lvwlen(v) + l_{vw}

Dijkstra算法的每次迭代处理一个新顶点,即一条从X跨越到V−X的边的头顶点

一个简单的实例

一个简单的图

现在,集合 X 包含了所有的顶点,不再有任何边从 X 跨越到 V-X 了,因此算法就宣告结束。

证明 Dijkstra 算法的正确性

定理(Dijkstra 算法的正确性)对于每个有向图 G=(V,E)G=(V,E),每个起始顶点 ss 并且所有边长均为 非负值,对于每个顶点 vVv\in V,Dijkstra 算法的结论 len(v)=dist(s,v)len(v)=dist(s,v) 都成立。

归纳证法

我们的计划是根据主循环的迭代数量进行归纳,逐个计算 Dijkstra 算法的最短路径长度的正确性。

断言:定义 P(k)P(k) 为在 Dijkstra 算法中,对于第 kk 个添加到 XX 中的顶点 vv,存在 len(v)=dist(s,v)len(v)=dist(s,v).

通过数学归纳法进行证明的分为一个基础条件和一个归纳步骤两个部分。

基本条件:证明 P(1)P(1) 是正确的。

我们知道第一个顶点 s 是起始顶点。因此 s 到它本身的最短路径是一条空路径,长度为 0。因此,len(s)=0=dist(s,s)len(s)=0=dist(s,s),这就证明了 P(1)P(1) 是成立的。

对于归纳步骤,我们选择 k>1k > 1 并假设 P(1),,P(k1)P(1),\dots,P(k-1) 都是正确的。对于 Dijkstra 算法添加到 XX 的前 k1k-1 个顶点,len(v)=dist(s,v)len(v)=dist(s,v)

ww^* 表示添加到 XX 的第 kk 个顶点,并用 (vw)(v^*,w^*) 表示在对应的那次迭代中所选的边。(此时 vv^* 必然已经在 XX 中)

算法把 len(w)len(w^*) 赋值为这条边的得分,即 len(v)+lvwlen(v^*)+l_{v^*w^*}。我们希望这个值和真正的最短路径长度 dist(s,w)dist(s,w^*) 相同。

我们分两个部分来证明这个结论的正确性。

Part 1

首先,我们证明真正的长度 dist(s,w)dist(s,w^*) 只可能小于算法所推测的 len(w)len(w^*),即 dist(s,w)len(w)dist(s,w^*)\leqslant len(w^*)

由于当边 (v,w)(v^*,w^*) 被选中时,vv^* 已经在 XX 中,因此它是添加到 XX 的前 k1k-1 个顶点之一。

根据归纳假设,Dijkstra 算法正确地计算了 vv^* 的最短路径长度:len(v)=dist(s,v)len(v^*)=dist(s,v^*).

具体来说,存在一条从 ssvv^* 的路径 PP,它的长度正好是 len(v)len(v^*)

PP 的末端添加 (v,w)(v^*,w^*) 就产生了一条从 ssww^* 的路径 PP^*,长度是 len(v)+lvw=len(w)len(v^*)+l_{v^*w^*}=len(w^*)

sws-w^* 路径的最短路径除了候选路径 PP^* 之外不会再有其它路径,因此 dist(s,w)dist(s,w^*) 不可能大于 len(w)len(w^*)

Part 2

现在,我们讨论反方向的不等式 dist(s,w)len(w)dist(s,w^*)\geqslant len(w^*)。换言之,我们希望证明路径 PP^* 确实是 sws-w^* 的最短路径,与之竞争的 sws-w^* 路径的长度都不会小于 len(w)len(w^*)

我们随意选一条 sws-w^* 的竞争路径 PP',我们不了解 PP'。但是,我们知道它源自于 ss,终于 ww^*。迭代之初,ss 属于集合 XX,但 ww^* 不属于 XX

由于它是从 XX 中开始并在 XX 之外结束,因此路径 PP' 跨越了 XXVXV-X 的边界 1 次。

至少跨越 X 到 V-X 一次

(y,z)(y,z) 表示跨越边界的 PP' 的第 1 条边(yXy\in XzXz\notin X)。

为了论证 PP' 的长度不会小于 len(w)len(w^*),我们独立考虑它的三个部分:

起始部分不可能小于 ssyy 的最短路径 dist(s,y)dist(s,y);边 (y,z)(y,z) 的长度是 lyzl_{yz}

最终部分我们并不是很清楚。但是,我们知道所有的边长度都是非负的。也就是说,它的总长度至少是 0。

将这三个部分的下界相加:

len(P)dist(s,y)+lyz+0len(P')\geqslant dist(s,y)+l_{yz}+0

我们需要把下界和算法决策时的得分进行关联。

由于 yXy\in X,因此它是前 k1k-1 个添加到 XX 的顶点之一。基于归纳假设,表明该算法会正确地计算它的最终路径长度:dist(s,y)=len(y)dist(s,y)=len(y)

因此:

len(P)len(y)+lyzlen(P')\geqslant len(y)+l_{yz}

右边正好是边 (y,z)(y,z) 的 Dijkstra 算法得分。由于总会选择最低的得分,并且因为在这次迭代中选了 (v,w)(v^*,w^*) 而不是 (y,z)(y,z),所以前者的 Dijkstra 得分更低:

len(v)+lvwlen(y)+lyzlen(v^*) + l_{v^*w^*}\leqslant len(y) + l_{yz}

因此:

len(P)len(v)+lvw=len(w)len(P')\geqslant len(v^*) + l_{v^*w^*}=len(w^*)

这样就完成归纳步骤的第二部分,得出结论:

对于添加到集合 XX 的每个顶点 vv,存在 len(v)=dist(s,v)len(v)=dist(s,v)

最终验证

现在考虑一个从来没有被添加到 XX 的顶点 vv。当这个算法结束时,len(v)=+len(v)=+\infty 并且没有任何边从 XX 跨越到 VXV-X

这意味着不存在从 ssvv 的路径,因为这样的路径肯定会在某一处跨越边界。

因此,dist(s,v)=+dist(s,v)=+\infty

我们可以得出结论,对于每个顶点 vv,如果 len(v)=dist(s,v)len(v)=dist(s,v),算法就会终止。不管 vv 是否被添加到 XX

至此,我们完成了整个证明过程。

一些新的研究

HHR+24

Universal Optimality of Dijkstra via Beyond-Worst-Case Heaps 这篇论文证明,Dijkstra 结合使用足够高效的数据结构时,在执行时间和比较次数方面具有普遍最优性(Universal Optimality)。

长久以来,Dijkstra 算法在最差情况下被认为是最佳的,例如使用 Fibonacci 堆时算法复杂度 O(m+nlog(n))O(m+nlog(n))。这篇论文进一步提升了对优势的理解,证明了在特定问题下(需要按距离排序输出结果),如果结合适当的数据结构。Dijkstra 实际上是普遍最优的。

DMMSY25

Breaking the Sorting Barrier for Directed Single-Source Shortest Paths 首次打破 Dijkstra 算法在稀疏图上的 O(m+nlog(n))O(m+nlog(n)) 时间限制。论文指出,如果不要求按距离排序输出结果,那么 Dijkstra 算法并不是最佳的。论文提出了 O(mlog23n)O(m log_{\frac{2}{3}} n) 的算法。

本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自小谷的随笔

上一篇
均值与标准差
下一篇
正态分布