算法复习——图算法篇之单源最短路径之Dijkstra算法
以下内容主要参考中国大学MOOC《算法设计与分析》,墙裂推荐希望入门算法的童鞋学习!
1. 问题背景
如何计算带权图中源点到所有其他顶点的最短路径?
2. 问题定义
单源最短路径问题(边权为正)(Single Source Shortest Paths Problem with Positive Weights)
输入:
- 带权图 G = < V , E , W > G=<V, E, W> G=<V,E,W>,其中 w ( u , v ) ≥ 0 w(u, v) \geq 0 w(u,v)≥0(圈中所有边权为正), ( u , v ) ∈ E (u, v) \in E (u,v)∈E
- 源点编号 s s s
输出:
- 源点 s s s到所有其他顶点 t t t的最短距离 δ ( s , t ) \delta(s, t) δ(s,t)和最短路径 < s , … , t > <s, \dots, t> <s,…,t>
3. 算法思想
- 辅助数组
-
d
i
s
t
dist
dist表示距离上界(估计距离)
- 源点 s s s, d i s t [ s ] = 0 dist[s]=0 dist[s]=0;其他顶点 u u u, d i s t [ u ] dist[u] dist[u]初始化为∞
- d i s t [ u ] dist[u] dist[u]:源点 s s s到顶点 u u u的距离上界, δ ( s , u ) ≤ d i s t [ u ] \delta(s, u) \leq dist[u] δ(s,u)≤dist[u]
-
c
o
l
o
r
color
color表示顶点状态
- 黑色:到顶点 u u u最短路已被确定
- 白色:到顶点 u u u最短路尚未确定
-
p
r
e
d
pred
pred表示前驱顶点
- ( p r e d [ u ] , u ) (pred[u], u) (pred[u],u)为最短路径上的边
-
d
i
s
t
dist
dist表示距离上界(估计距离)
- 核心思想
- 步骤1:新建空的黑色顶点集 V A V_A VA
- 步骤2:选择一个白色顶点变为黑色(到该顶点最短路被确定)
- 步骤3:重复步骤2,直至所有顶点均为黑色
针对这个核心思想,可以提出以下两个问题:
-
问题1:选择哪个白色顶点变为黑色?采用贪心策略
- 对每个顶点 y ∈ V − V A y \in V-V_A y∈V−VA,都有一个估计距离 d i s t [ y ] dist[y] dist[y]
- 选择估计距离最小的顶点 v v v, d i s t [ v ] ≤ d i s t [ y ] dist[v] \leq dist[y] dist[v]≤dist[y], v , y ∈ V − V A v,y \in V-V_A v,y∈V−VA
-
问题2:如何更新每个顶点的估计距离?
- 当前到顶点 v v v的最短路径: < s , w , v > <s, w, v> <s,w,v>,距离为 d i s t [ v ] dist[v] dist[v]
- 通过顶点 u u u的新路径: < s , … , u , v > <s, \dots, u, v> <s,…,u,v>,距离为 d i s t [ u ] + w ( u , v ) dist[u]+w(u, v) dist[u]+w(u,v)
- 如果新路径更短(
d
i
s
t
[
u
]
+
w
(
u
,
v
)
<
d
i
s
t
[
v
]
dist[u]+w(u, v)<dist[v]
dist[u]+w(u,v)<dist[v])
- 更新 d i s t [ v ] dist[v] dist[v]: d i s t [ v ] = d i s t [ u ] + w ( u , v ) dist[v]=dist[u]+w(u, v) dist[v]=dist[u]+w(u,v)
- 该操作被称为松弛操作
4. 伪代码
Dijkstra(G, S)
输入:图G=<V, E, W>,源点s
输出:单源最短路径P
新建一维数组color[1..|V|],dist[1..|V|],pred[1..|V|]
// 初始化
for u ∈ V do
color[u] ← WHITE
dist[u] ← ∞
pred[u] ← NULL
end
dist[s] ← 0
// 执行单源最短路径算法
for i ← 1 to |V| do
minDist ← ∞
rec ← 0
for j ← 1 to |V| do
if color[j] != BLACK and dist[j] < minDist then
minDist ← dist[j]
rec ← j
end
end
for u ∈ G.Adj[rec] do
if dist[rec] + w(rec, u) < dist[u] then
dist[u] ← dist[rec] + w(rec, u)
pred[u] ← rec
end
end
color[rec] ← BLACK
end
通过这种方法实现的时间复杂度是 O ( ∣ V ∣ 2 ) O(|V|^2) O(∣V∣2)。
5. 优先队列优化
但很显然我们可以通过优先队列,来加速查询。优先队列部分可参考算法复习——图算法篇之最小生成树之Prim算法。
Dijkstra-PriQueue(G, s)
输入:图G=<V, E, W>,源点s
输出:单源最短路径P
新建一维数组color[1..|V|],dist[1..|V|],pred[1..|V|]
新建空优先队列Q
// 初始化
for u ∈ V do
color[u] ← WHITE
dist[u] ← ∞
pred[u] ← NULL
end
dist[s] ← 0
Q.Insert(V, dist)
// 执行单源最短路径算法
while 优先队列Q非空 do
v ← Q.ExtractMin()
for u ∈ G.Adj[v] do
if dist[v] + w(u, v) < dist[u] then
dist[u] ← dist[v] + w(u, v)
pred[u] ← v
Q.DecreaseKey((u, dist[u]))
end
end
color[v] ← BLACK
end
所以经过优化后,该算法的时间复杂度是 O ( ∣ E ∣ l o g ∣ V ∣ ) O(|E|log|V|) O(∣E∣log∣V∣)
6. 算法正确性证明
-
定理:Dijkstra算法中,顶点 u u u被添加到 V A V_A VA时, d i s t [ u ] = δ ( s , u ) dist[u]=\delta(s, u) dist[u]=δ(s,u)
-
证明:
-
采用反证法,假设Dijkstra算法将顶点 u u u添加到 V A V_A VA时, d i s t [ u ] ≠ δ ( s , u ) dist[u] \neq \delta(s, u) dist[u]=δ(s,u)
-
由于 d i s t [ u ] dist[u] dist[u]作为 δ ( s , u ) \delta(s, u) δ(s,u)的上界,故 d i s t [ u ] > δ ( s , u ) dist[u] > \delta(s, u) dist[u]>δ(s,u)
-
应存在一条长度为 δ ( s , u ) \delta(s, u) δ(s,u)的从 s s s到 u u u的最短路径,不妨设其为 < s , … , x , y , … , u > <s, \dots, x, y, \dots, u> <s,…,x,y,…,u>,其中边 ( x , y ) (x, y) (x,y)横跨 < V A , V − V A > <V_A, V-V_A> <VA,V−VA>, x ∈ V A x \in V_A x∈VA, y ∈ V − V A y \in V - V_A y∈V−VA
-
-
算法令 V A V_A VA中的顶点满足: d i s t [ x ] = δ ( x , s ) dist[x]=\delta(x, s) dist[x]=δ(x,s), x ∈ V A x \in V_A x∈VA
-
< s , … , x , y > <s, \dots, x, y> <s,…,x,y>是最短路径 < s , … , x , y , … , u > <s, \dots, x, y, \dots, u> <s,…,x,y,…,u>的子路径,故:
- δ ( s , y ) = δ ( s , x ) + w ( x , y ) = d i s t [ x ] + w ( x , y ) \delta(s, y)=\delta(s, x)+w(x, y)=dist[x]+w(x, y) δ(s,y)=δ(s,x)+w(x,y)=dist[x]+w(x,y)(公式1)
-
算法对顶点 x x x出发的所有边(包括边 ( x , y ) (x, y) (x,y))已进行松弛操作,故:
- d i s t [ y ] ≤ d i s t [ x ] + w ( x , y ) dist[y] \leq dist[x] + w(x, y) dist[y]≤dist[x]+w(x,y)(公式2)
-
合并上述公式1和公式2,可得 d i s t [ y ] = δ ( s , y ) dist[y] = \delta(s, y) dist[y]=δ(s,y)
-
最短路径 s , … , x , y , … , u > s, \dots, x, y, \dots, u> s,…,x,y,…,u>中, y y y出现在 u u u之前,故:
- d i s t [ u ] > δ ( s , u ) ≥ δ ( s , y ) = d i s t [ y ] dist[u] > \delta(s, u) \geq \delta(s, y) = dist[y] dist[u]>δ(s,u)≥δ(s,y)=dist[y]
-
d i s t [ u ] > d i s t [ y ] dist[u] > dist[y] dist[u]>dist[y], u ≠ y u \neq y u=y, u u u不应是下一个被添加顶点,故产生矛盾
本质上,我们是证明了定理的逆否命题,若 d i s t [ u ] ≠ δ ( s , u ) dist[u] \neq \delta(s, u) dist[u]=δ(s,u),就不应该是 u u u被添加到 V A V_A VA,并使用了反证法。