前言
在最短路径问题中,给定一个带权重的有向图G=(V,E)和权重函数w:E→R,该权重函数将每条边映射到实数值的权重上。图中一条路径
p
=
<
v
0
,
v
1
,
…
,
v
k
>
p=\lt v_0,v_1,…,v_k\gt
p=<v0,v1,…,vk>的权重
w
(
p
)
w(p)
w(p)是构成该路径的所有边的权重之和:
w
(
p
)
=
∑
i
=
1
k
w
(
v
i
−
1
,
v
i
)
w(p)=\sum_{i=1}^kw(v_{i-1},v_i)
w(p)=i=1∑kw(vi−1,vi)
定义从结点u到结点v的最短路径权重δ(u,v)如下:
δ
(
u
,
v
)
=
{
m
i
n
{
w
(
p
)
:
A
→
p
B
}
if
存
在
一
条
u
到
v
结
点
的
路
径
∞
if
不
存
在
δ(u,v) = \begin{cases} min\{w(p):A \xrightarrow[]{p} B\} &\text{if } 存在一条u到v结点的路径 \\ \infty &\text{if } 不存在 \end{cases}
δ(u,v)={min{w(p):ApB}∞if 存在一条u到v结点的路径if 不存在
从结点u到结点v的最短路径则定义为任何一条权重为
w
(
p
)
=
δ
(
u
,
v
)
w(p)=δ(u,v)
w(p)=δ(u,v)的从u到v的路径p。
最短路径的几个变体
单源最短路径问题:给定一个图G=(V,E),希望找到从给定源结点s∈V到每个结点v∈V的最短路径。解决单源最短路径问题的思想可以用来解决下面的几个最短路径的变体问题:
- 单目的地最短路径问题:找到从每个结点v到给定目的地结点t的最短路径。如果将图的每条边的方向翻转过来,就可以将这个问题转换为单源最短路径问题。
- 单结点对最短路径问题:找到从给定结点u到给定结点v的最短路径。如果解决了针对单个结点u的单源最短路径问题,那么也就解决了这个问题。
- 所有结点对最短路径问题:对于每对结点u和v,找到从结点u到结点v的最短路径。虽然可以针对每个结点运行一遍单源最短路径算法,但通常可以更快地解决这个问题,将在下一篇博文中讨论此问题。
最短路径的最优子结构
最短路径算法通常依赖最短路径的一个重要性质:两个结点之间的一条最短路径包含着其他的最短路径。
最短路径的子路径也是最短路径
- 给定带权重的有向图G=(V,E)和权重函数w:E→R。
- 设p=<v0,v1,…,vk>为从结点v0到结点vk的一条最短路径,
- 对于任意的i和j,0≤i≤j≤k,设pij=<vi,vi+1,…,vj>为路径p中从结点vi到结点vj的子路径。
- 那么pij是从结点vi到结点vj的一条最短路径。
负权重的边
某些单源最短路径问题可能包括权重为负值的边。但环路权重总和不能为负值,如果图G包含从s可以达到的权重为负值的环路,则最短路径权重无定义。
环路
一条最短路径不能包含权重为负值的环路,也不能包含权重为正值的环路。因为只要将环路从路径上删除就可以得到一条源结点和终结点与原来路径相同的一条权重更小的路径。
一条最短路径上还有权重为0的环路,我们可以重复删除这些环路,直到得到一条不包括环路的最短路径。假定在找到的最短路径中没有环路,即它们都是简单路径
最短路径的表示
(略)
松弛操作
对于每个结点v来说,维持一个属性v.d,用来记录从源结点s到结点v的最短路径权重的上界,称v.d为s到v的最短路径估计。
使用下面运行时间为Θ(V)的算法来对最短路径估计和前驱结点进行初始化:
INITIALIZE-SINGLE-SOURCE(G,s)
for each vertex v∈G.V
v.d=∞
v.π=NIL
s.d=0
在初始化操作结束后,对于所有的结点v∈V,有v.π=NIL,且s.d=0,对于所有的结点v∈V-{s},有v.d=∞。
对一条边的(u,v)的松弛过程为:
- 从结点s到结点u之间的最短路径距离加上结点u与v之间的边权重,
- 与当前的s到v的最短路径估计进行比较,如果前者更小,则对v.d和v.π进行更新。
松弛步骤可能降低最短路径的估计值v.d并更新v的前驱属性v.π。下面的伪代码执行的就是对边(u,v)在O(1)时间内进行的松弛操作:
RELAX(u,v,w)
if v.d>u.d+w(u,v)
v.d=u.d+w(u,v)
v.π=u
最短路径和松弛操作的性质
为了证明本博文所讨论算法的正确性,需要使用最短路径和松弛操作如下的一些性质.
- 三角不等式性质:对于任何边(u,v)∈E,有δ(s,v)≤δ(s,u)+w(u,v)。
- 上界性质:对于所有的结点v∈V,总是有v.d≥δ(s,v),且一旦v.d的取值达到δ(s,v),其值将不再发生变化。
- 非路径性质:如果从结点s到结点v之间不存在路径,则总是有v.d=δ(s,v)=∞。
- 收敛性质:对于某些结点u,v∈V,如果s→u→v是图G中的一条最短路径,并且在对边(u,v)进行松弛前的任意时间有u.d=δ(s,u),则在之后的所有时间有v.d=δ(s,v)。
- 路径松弛性质:如果p=<v0,v1,…,vk>是从源结点s=v0到结点vk的一条最短路径,则规定对p中的边所进行松弛的次序为(v0,v1),(v1,v2),…,(vk-1,vk),且有vk.d=δ(s,vk)。
- 前驱子图性质:对于所有的结点v∈V,一旦v.d=δ(s,v),则前驱子图是一棵根结点为s的最短路径树。
Bellman-Ford算法
Bellman-Ford算法解决的是一般情况下的单源最短路径问题,边的权重可以为负值。
# 算法返回一个布尔值,以表明是否存在一个从源结点可以到达的权重为负值的环路。
# 如果存在这样一个环路,算法将显示不存在解决方案的输出信息。
# 如果没有这种环路存在,算法将给出最短路径和它们的权重。
BELLMAN-FORD(G,w,s)
# 初始化所有的点的d,s.d=0
INITIALIZE-SINGLE-SOURCE(G,s)
# 算法对每条边进行|V|-1次循环松
for i=1 to |G.V|-1
for each edge(u,v)∈G.E
RELAX(u,v,w)
# 查看有没有负值环
for each edge(u,v)∈G.E
if v.d>u.d+w(u,v)
return FALSE
return TRUE
Bellman-Ford算法通过对边进行松弛操作来渐近地降低从源结点s到每个结点v的最短路径的估计值v.d,直到该估计值与实际的最短路径权重δ(s,v)相同时为止。时间复杂度:O(V*E)
有向无环图中的单源最短路径问题
Directed Acyclic Graph,DAG,有向无环图。因为不存在环的情况, 可根据结点的拓扑排序次序来对带权重的有向无环图G=(V,E)进行边的松弛操作,便可以在Θ(V+E)时间内计算出从单个源结点到所有结点之间的最短路径。
DAG-SHORTEST-PATHS(G,w,s)
# 对点进行拓扑排序
topologically sort the vertices of G
# 初始化所有的点的d,s.d=0
INITIALIZE-SINGLE-SOURCE(G,s)
# 根据拓扑排序的顺序对每个边进行松弛
for each vertex u, taken in topologically sorted order
for each vertex v∈G.Adj[u]
RELAX(u,v,w)
ps:在有向无环图中,即使存在权重为负值的边,但因为没有权重为负值的环路,最短路径都是存在的。
Dijkstra算法(贪心)
Dijkstra算法解决的是带权重的有向图上单源最短路径问题,要求所有边的权重都为非负值。即对于所有的边(u,v)∈E,都有w(u,v)≥0。Dijkstra算法在运行过程
- 维持的关键信息是一组结点集合S,其中从源结点s到该集合中每个结点之间的最短路径已经被找到。
- 算法重复从结点集V-S中选择最短路径估计最小的结点u,将u加入到集合S,
- 然后对所有从u发出的边进行松弛。
在下面给出的实现方式中,使用一个最小优先队列Q来保存结点集合,每个结点的关键值为其d值。
DIJKSTRA(G,w,s)
# 初始化所有的点的d,s.d=0
INITIALIZE-SINGLE-SOURCE(G,s)
S=∅
# 表示V-S
Q=G.V
while Q≠∅
# 从结点集Q中选择最短路径估计最小的结点u,将u加入到集合S
u=EXTRACT-MIN(Q)
S=S∪{u}
# 从u发出的边进行松弛
for each vertex v∈G.Adj[u]
RELAX(u,v,w)
Dijkstra算法的运行时间依赖于最小优先队列的实现
- 数组存储,则 T=O(V²)
- 二项式堆存储,则T=O((V+E)lgV)
- 斐波拉契堆存储,则T=O(E+VlgV)
主要参考
《算法导论-上课笔记11:单源最短路径》
《《算法导论》| 第二十四章 单源最短路径》
《Single-Source Shortest Paths》