单源最短路径(Bellman-Ford、Dijkstra)

单源最短路径

  本文参考整理及图片来源:《算法导论》P374-P386

1 预备知识

1.1 最短路径问题

  假设你需要找到一条从 A A A市到 B B B市的一条最短路径,先给定一幅道路交通图,上面标有所有相邻城市之间的距离。你要怎样才能找出 A A A B B B的最短距离呢?

  要解决这个问题,我们最直观的想法就是把 A A A B B B的所有路径都找出来,计算这些路径的长度,选取其中最短的路径。但这种方法太麻烦了,我们要检查许多种可能的路径,而且大部分路径都不值得检查。因此需要探索如何高效地解决这个问题。

  在最短路径问题中,我们给定一个带权重的有向图 G = ( V , E ) G=(V,E) G=(V,E)和权重函数 w : E → R w:E\rightarrow R w:ER,该权重函数将每条边映射到实数值的权重上。图中一条路径 p = ⟨ v 0 , v 1 , ⋯   , v k ⟩ p=\langle v_0,v_1,\cdots,v_k\rangle 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=1kw(vi1,vi)

定义从节点 u u u到节点 v v v最短路径权重 δ ( u , v ) \delta(u, v) δ(u,v)如下:
δ ( u , v ) = { m i n { w ( p ) : u → p v } 如 果 存 在 一 条 从 节 点 u 到 节 点 v 的 路 径 ∞ 其 他 \delta(u, v)=\begin{cases} min\{w(p):u\stackrel{p}{\rightarrow} v\} & 如果存在一条从节点u到节点v的路径\\ \infty&其他 \end{cases} δ(u,v)={min{w(p):upv}uv
从节点 u u u到节点 v v v最短路径则定义为一条权重为 w ( p ) = δ ( u , v ) w(p)=\delta(u, v) w(p)=δ(u,v)的从 u u u v v v的路径 p p p

  那么单源最短路径问题为:给定一个图 G = ( V , E ) G=(V,E) G=(V,E),我们希望找到从给定源节点 s ∈ V s\in V sV到每个节点 v ∈ V v\in V vV的最短路径。

1.2 最短路径的几个变体问题

  单源最短路径可以解决几个变体问题:

  • 单目的地最短路径问题:找到每个节点 v v v到给定目的地的节点 t t t的最短路径。
  • 单节点对最短路径问题:找到从给定节点 u u u到给定节点 v v v的最短路径。
  • 所有节点对最短路径问题:对于每对节点 u u u v v v,找到从节点 u u u到节点 v v v的最短路径。

1.3 最短路径的最优子结构

  最短路径算法通常依赖最短路径的一个重要性质:两个节点之间的一条最短路径包含着其他的最短路径,下述引理精确地描述了最短路径的最优子结构性质。

  最短路径的子路径也是最短路径:给定带权重的有向图 G = ( V , E ) G=(V,E) G=(V,E)和权重函数 w : E → R w:E\rightarrow R w:ER。设 p = ⟨ v 0 , v 1 , ⋯   , v k ⟩ p=\langle v_0,v_1,\cdots,v_k\rangle p=v0,v1,,vk为从节点 v 0 v_0 v0到节点 v k v_k vk的一条最短路径,并且对于任意的 i i i j j j 0 ≤ i ≤ j ≤ k 0\le i\le j\le k 0ijk,设 p i j = ⟨ v i , v i + 1 , ⋯   , v j ⟩ p_{ij}=\langle v_i,v_{i+1},\cdots,v_j\rangle pij=vi,vi+1,,vj为路径 p p p中从节点 v i v_i vi v j v_j vj的子路径。那么 p i j p_{ij} pij是从节点 v i v_i vi到节点 v j v_j vj的一条最短路径。

1.4 负权重的边

  某些单源最短路径问题可能包括权重为负值的边,下面来看看负权重的边可能带来的一些问题,并以一个实例进行讲解。

  如果图 G = ( V , E ) G=(V,E) G=(V,E)不包含从源节点 s s s可以到达的权重为负值的环路(即环路中的边权重和为负值),则对于所有的节点 v ∈ V v\in V vV,最短路径权重 δ ( s , v ) \delta(s,v) δ(s,v)都有精确定义,即便其取值为负数。

  相反,如果图 G = ( V , E ) G=(V,E) G=(V,E)包含从源节点 s s s可以到达的权重为负值的环路,则最短路径权重无定义。(因为我们只要沿着任何“最短”路径再遍历一次权重为负值的环路,就可以找到一条权重更小的路径)。

  如果从节点 s s s v v v某条路径上存在权重为负值的环路,那我们定义 δ ( s , v ) = − ∞ \delta(s,v)=-\infty δ(s,v)=

  下面是一个例子,帮助理解这段话。根据下图,求源节点 s s s到其它所有节点的最短路径权重。
在这里插入图片描述

  • δ ( s , a ) = w ( s , a ) = 3 \delta(s,a)=w(s,a)=3 δ(s,a)=w(s,a)=3 s s s a a a只有一条路径;
  • δ ( s , b ) = w ( s , a ) + w ( a , b ) = − 1 \delta(s,b)=w(s,a)+w(a,b)=-1 δ(s,b)=w(s,a)+w(a,b)=1,原因同上;
  • δ ( s , c ) = w ( s , c ) = 5 \delta(s,c)=w(s,c)=5 δ(s,c)=w(s,c)=5 s s s c c c有无数条路径, ⟨ s , c ⟩ , ⟨ s , c , d , c ⟩ \langle s,c\rangle,\langle s,c,d,c\rangle s,c,s,c,d,c等,由于 c , d c,d c,d组成的环路权重为 3 > 0 3\gt 0 3>0,因此最短路径权重应该不含环;
  • δ ( s , d ) = w ( s , c ) + w ( c , d ) = 11 \delta(s,d)=w(s,c)+w(c,d)=11 δ(s,d)=w(s,c)+w(c,d)=11,原因同上;
  • δ ( s , e ) = − ∞ \delta(s,e)=-\infty δ(s,e)= s s s e e e有无数条路径, ⟨ s , e ⟩ , ⟨ s , e , f , e ⟩ \langle s,e\rangle,\langle s,e,f,e\rangle s,e,s,e,f,e,由于 e , f e,f e,f组成的环路权重为 − 3 < 0 -3\lt 0 3<0,因此总能找到更小的权重,故为 − ∞ -\infty
  • δ ( s , f ) = − ∞ \delta(s,f)=-\infty δ(s,f)=,原因同上;
  • δ ( s , g ) = − ∞ \delta(s,g)=-\infty δ(s,g)=,由于 s s s g g g的某条路径上存在权重为负值的环路,故为 − ∞ -\infty
  • δ ( s , h ) = δ ( s , i ) = δ ( s , j ) = ∞ \delta(s,h)=\delta(s,i)=\delta(s,j)=\infty δ(s,h)=δ(s,i)=δ(s,j)=,这三个节点根本无法到达,故为 ∞ \infty

1.5 环路

  思考一个问题,一条最短路径可以包含环路吗

  首先它肯定是不能包含权重为负值的环路了;其次如果它包含权重为正的环路,那我们只要将环路从路径上删除就可以得到一条权重更小的路径;这样就只剩下权重为 0 0 0的环路了,这时我们也可以从任何路径上删除权重为 0 0 0的路径而得到另一条权重相同的路径。

  因此,不失一般性,我们可以假定在找到的最短路径中没有环路,即它们都是简单路径。由于图 G = ( V , E ) G=(V,E) G=(V,E)中的任意无环路径最多包含 ∣ V ∣ |V| V个不同的节点,它也最多包含 ∣ V ∣ − 1 |V|-1 V1条边。因此我们可以将注意力集中到至多只包含 ∣ V ∣ − 1 |V|-1 V1条边的最短路径上。

1.6 最短路径的表示

  通常情况下,我们不但希望计算出最短路径权重,还希望计算出最短路径上的节点。给定图 G = ( V , E ) G=(V,E) G=(V,E),对于每个节点 v v v,我们维持一个前驱节点 v . π v.\pi v.π。该前驱节点可能是另一个节点或者 N o n e None None。最短路径算法将对每个节点的 π \pi π属性进行设置,这样,将从节点 v v v开始的前驱节点链反转过来,就是 s s s v v v的一条最短路径。

  但是,在最短路径算法过程中, π \pi π值不一定能给出最短路径。我们感兴趣的是由 π \pi π值所诱导的前驱子图 G π = ( V π , E π ) G_{\pi}=(V_{\pi}, E_{\pi}) Gπ=(Vπ,Eπ)其中:

V π = { v ∈ V : v . π ≠ N o n e } ∪ { s } E π = { ( v . π , v ) ∈ E : v ∈ V π − { s } } V_{\pi}=\{v\in V:v.\pi\ne None\} \cup \{s\}\\ E_{\pi}=\{(v.\pi, v)\in E:v\in V_{\pi}-\{s\}\} Vπ={vV:v.π=None}{s}Eπ={(v.π,v)E:vVπ{s}}

  最短路径算法终止时所产生的 G π G_{\pi} Gπ是一棵根节点为 s s s的”最短路径树“(一个有向子图),该树包含了从源节点 s s s到每个可以从 s s s到达的节点的一条最短路径。

  注意:最短路径不一定唯一,最短路径树也不一定唯一。下图是一个带权重的有向图和两棵根节点相同的最短路径树。

在这里插入图片描述

1.7 松弛操作

  最短路径算法需要使用松弛技术。对于每个节点 v v v来说,我们维持一个属性 v . d v.d v.d用来记录从源节点 s s s到节点 v v v的最短路径权重的上界。我们将 v . d v.d v.d称之为 s s s v v v最短路径估计。下面的伪代码用来对 v . d v.d v.d v . π v.\pi v.π进行初始化:

I N I T I A L I Z E − S I N G L E − S O U R C E ( G , s ) INITIALIZE-SINGLE-SOURCE(G,s) INITIALIZESINGLESOURCE(G,s):初始化 v . d v.d v.d v . π v.\pi v.π

for each vertex v ∈ G.V:
	v.d =//初始化每个节点的最短路径估计为无穷大,也就是上界为无穷
	v.π = None			//初始化前驱节点为None
end for
s.d = 0					//初始化源节点的最短路径估计为0

  对每一条边 ( u , v ) (u,v) (u,v)松弛过程为:首先测试一下是否可以对从 s s s v v v的最短路径进行改善。测试的方法是,将从节点 s s s到节点 u u u之间的最短路径距离加上节点 u u u v v v之间的边的权重,并与当前的 s s s v v v的最短路径估计进行比较,如果前者更小,则对 v . d v.d v.d v . π v.\pi v.π进行更新。松弛步骤可能降低最短路径的估计值 v . d v.d v.d并更新 v v v的前驱属性 v . π v.\pi v.π。下面的伪代码就是对边 ( u , v ) (u, v) (u,v)的松弛操作:

R E L A X ( u , v , w ) RELAX(u,v,w) RELAX(u,v,w):对 ( u , v ) (u,v) (u,v)进行松弛操作。

if v.d > u.d + w(u, v):
	v.d = u.d + w(u, v)
	v.π = u
end if

下图展示了对两条边进行松弛操作的例子,第一个 v . d v.d v.d因松弛而减小,第二个则没变。
在这里插入图片描述

  下面介绍的每个算法都会调用 I N I T I A L I Z E − S I N G L E − S O U R C E INITIALIZE-SINGLE-SOURCE INITIALIZESINGLESOURCE R E L A X RELAX RELAX

1.8 最短路径和松弛操作的性质

  为了证明后面算法的正确性,需要使用最短路径和松弛操作的一些性质(证明略)。

  • 三角不等式性质:对于任何边 ( u , v ) ∈ E (u,v)\in E (u,v)E,我们有 δ ( s , v ) ≤ δ ( s , u ) + w ( u , v ) \delta(s,v)\le \delta(s, u) + w(u, v) δ(s,v)δ(s,u)+w(u,v)
  • 上界性质:对于所有的节点 v ∈ V v\in V vV,我们总是有 v . d ≥ δ ( s , v ) v.d\ge \delta(s,v) v.dδ(s,v)。一旦 v . d v.d v.d的取值达到 δ ( s , v ) \delta(s,v) δ(s,v),其值将不再发生变化。
  • 非路径性质:如果从节点 s s s到节点 v v v之间不存在路径,则总是有 v . d = δ ( s , v ) = ∞ v.d=\delta(s,v)=\infty v.d=δ(s,v)=
  • 收敛性质:对于某些节点 u , v ∈ V u,v\in V u,vV,如果 s → ⋯ → u → v s\rightarrow \cdots \rightarrow u\rightarrow v suv是图 G G G中的一条最短路径,并且在对边 ( u , v ) (u, v) (u,v)进行松弛前的任意时间有 u . d = δ ( s , u ) u.d=\delta(s,u) u.d=δ(s,u),则在之后的所有时间有 v . d = δ ( s , v ) v.d=\delta(s,v) v.d=δ(s,v)
  • 路径松弛性质:如果 p = ⟨ v 0 , v 1 , ⋯   , v k ⟩ p=\langle v_0,v_1,\cdots,v_k\rangle p=v0,v1,,vk是从源节点 s = v 0 s=v_0 s=v0到节点 v k v_k vk的一条最短路径,并且我们对 p p p中的边进行松弛的次序为 ( v 0 , v 1 ) , ( v 1 , v 2 ) , ⋯   , ( v k − 1 , v k ) (v_0, v_1),(v_1,v_2),\cdots ,(v_{k-1},v_k) (v0,v1),(v1,v2),,(vk1,vk),则 v k . d = δ ( s , v k ) v_k.d=\delta (s,v_k) vk.d=δ(s,vk)。该性质的成立与任何其他的松弛操作无关,即使这些松弛操作是与对 p p p上的边所进行的松弛操作穿插进行的。
  • 前驱子图性质:对于所有的节点 v ∈ V v\in V vV,一旦 v . d = δ ( s , v ) v.d=\delta(s,v) v.d=δ(s,v),则前驱子图是一棵根节点为 s s s的最短路径树。

2 Bellman-Ford 算法

  Bellman-Ford算法解决的是一般情况下的单源最短路径问题,所谓一般情况是指边的权重可以为负值。给定一个带权重的有向图 G = ( V , E ) G=(V,E) G=(V,E)和权重函数 w : E → R w:E\rightarrow R w:ER,Bellman-Ford算法返回一个布尔值,以表明是否存在源节点可到达的权重为负值的环路,如果存在则算法告诉我们不存在解决方案;如果不存在则算法将给出最短路径和它们的权重。

  Bellman-Ford算法通过对边进行松弛操作来渐近地降低从源节点 s s s到每个节点 v v v的最短路径的估计值 v . d v.d v.d,直到该估计值与实际的最短路径权重 δ ( s , v ) \delta(s,v) δ(s,v)相同时为止。该算法返回 T R U E TRUE TRUE当且仅当输入图不包含可以从源节点到达的权重为负值的环路。下面是Bellman-Ford算法的伪代码描述:

B e l l m a n − F o r d ( G , w , s ) Bellman-Ford(G,w,s) BellmanFord(G,w,s):解决单源最短路径问题。

INITIALIZE-SINGLE-SOURCE(G, s)	//初始化v.d和v.π
for i = 1 to |G.V|-1:			//对每一条边进行|G.V|-1次处理
	for each edge(u, v) ∈ G.E:
		RELAX(u, v, w)
	end for 
end for
for each edge(u, v) ∈ G.E:
	if v.d > u.d + w(u, v):
		return False
	end if
end for
return True

  下图展示了Bellman-Ford的算法流程,这里每一次的松弛操作对边的处理次序都是: ( t , x ) , ( t , y ) , ( t , z ) , ( y , x ) , ( y , z ) , ( z , x ) , ( z , s ) , ( s , t ) , ( s , y ) (t,x),(t,y),(t,z),(y,x),(y,z),(z,x),(z,s),(s,t),(s,y) (t,x),(t,y),(t,z),(y,x),(y,z),(z,x),(z,s),(s,t),(s,y)
在这里插入图片描述

3 有向无环图中的单源最短路径问题

  根据节点的拓扑排序次序来对带权重的有向无环图 G = ( V , E ) G=(V,E) G=(V,E)进行边的松弛操作,我们便可以在 O ( V + E ) O(V+E) O(V+E)的时间内计算出从单个源节点到所有节点之间的最短路径。在有向无环图中,即时存在权重为负值的边,但因为没有权重为负值的环路,最短路径都是存在的。

  算法流程就是:首先对有向无环图进行拓扑排序,以便确定节点之间的一个线性词序,然后只需要按照拓扑排序的次序对节点进行一遍处理即可。每次对一个节点进行处理时,我们对从该节点出发的所有的边进行松弛操作。算法的伪代码描述如下:

D A G − S H O R T E S T − P A T H S ( G , w , s ) DAG-SHORTEST-PATHS(G,w,s) DAGSHORTESTPATHS(G,w,s):计算源节点 s s s到所有节点之间的最短路径。

topologically sort the vertices of G		//对G进行拓扑排序
INITIALIZE-SINGLE-SOURCE(G,s)				//初始化v.d和v.π
for each vertex u, taken in topologically sorted order:
	for each vertex v ∈ G.Adj[u]:
		RELAX(u,v,w)

  下图展示了这个过程,其中 ( a ) (a) (a)为拓扑排序并初始化 v . d v.d v.d v . π v.\pi v.π的状态, ( b ) (b) (b) ( g ) (g) (g)表示的是对每个当前节点的邻接边进行松弛操作之后的结果,粗边表示 v . π v.\pi v.π
在这里插入图片描述

4 Dijkstra 算法

  Dijkstra算法解决的是带权重的有向图上单源最短路径问题,该算法要求所有边的权重都为非负值

  Dijkstra算法在运行过程中维持的关键信息是一组节点集合 S S S。从源节点 s s s到该集合中每个节点之间的最短路径已经被找到。算法重复从节点集 V − S V-S VS中选择最短路径估计最小的节点 u u u,将 u u u加入到集合 S S S,然后对所有从 u u u出发的边进行松弛。因此我们需要采用一个最小优先队列 Q Q Q来保存节点集合,每个节点的关键字为其 d d d值。算法的伪代码描述如下:

D I J K S T R A ( G , w , s ) DIJKSTRA(G,w,s) DIJKSTRA(G,w,s):对不含负权重的图计算单源最短路径。

INITIALIZE-SINGLE-SOURCE(G,s)
S = ∅
Q = G.V
while Q !=:
	u = EXTRACT-MIN(Q)
	S = S ∪ {u}
	for each vertex v ∈ G.Adj[u]:
		RELAX(u, v, w)
	end for
end while

  下面图展示了Dijkstra算法的过程。
在这里插入图片描述

5 一个练习

  下面这道题就是单源最短路径问题,可以使用Bellman-ford和Dijkstra两种方法实现:

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值