演算法導論筆記:24單源最短路徑
最短路徑問題:一個帶權重的有向圖G = (V, E)和權重函式w: E->R,該權重函式將每條邊對映到實數值的權重上。一條路徑p的權重w(p)是構成該路徑的所有邊的權重之和,定義從節點u到結點v的最短路徑權重δ (u, v)如下:
在實際應用中,可以用一張圖表示道路交通圖,結點代表城市,邊代表城市之間的道路,邊上的權重代表道路的長度。目標就是找出一條從城市A到城市B的最短路徑,邊上的權重還可以表示時間、費用、罰款、損失或者任何隨路徑長度的增加而線性積累的,並且需要最小化的數量。
廣度優先搜尋演算法就是一個求取最短路徑的演算法,但該演算法只能用於無權重的圖,即每條邊的權重都是單位權重的圖。
本章討論單源最短路徑問題:給定圖G=(V, E),希望找到從給定源節點s到每個結點v的最短路徑。單源最短路徑可以有下面的變體:
單目的地最短路徑問題:找出從每個頂點v到指定終點t的最短路徑。把圖中的每條邊反向,就可以把這一問題變為單源最短路徑問題。
單結點對最短路徑問題:找到從給定結點u到給定結點v的最短路徑。解決了單源最短路徑問題,則這一問題也就獲得解決。
所有節點對最短路徑問題:對於每對頂點u和v,找出從u到v的最短路徑。雖然將每個頂點作為源點,執行一次單源演算法就可以解決這一問題,但通常可以更快地解決這一問題,下一章將詳細討論這個問題。
一:概述
1:最短路徑的最優子結構
最短路徑具有最優子結構性質:兩個結點之間的一條最短路徑包含其他的最短路徑:
給定帶權重的有向圖G=(V, E)和權重函式w。設p=< >為從節點 到結點 的一條最短路徑,並且對於任意的i, j, 0 <= i <= j <= k,設 = < >為路徑p中從節點 到結點 的子路徑。那麼是從到節點的一條最短路徑。
2:負權重的邊
邊的權重可以為負值,即使邊的權重為負值,對於所有節點v,最短路徑權重 (u, v)都可以有精確定義。但是,如果圖G=(V, E)包含從源節點s可以到達的,權重為負值的環路,則最短路徑權重無定義
如下圖所示:
從節點s到節點c有無數條路徑;<s,c>, <s,c,d,c>,<s,c,d,c,d,c>等,因為環路<c, d, c>的權重為3>0。所以從s到c的最短路徑為<s,c>,δ(s, c)=5。
從節點s到節點e也有無數條路徑:<s,e>, <s,e,f,e>, <s,e, f,e, f,e >等,因為環路<e, f, e>的權重為-3,所以,從節點s到節點e沒有最短路徑,因此δ(s,e)=-∞。類似的, δ(s,f)=-∞。因為g可以從節點f到達,所以,δ(s, g)=-∞。
節點<h,i,j>也形成一個權重為負數的環路,但是他們不能從節點s到達,所以: (s, h) = (s, i) = (s, j) = ∞ 。
4:環路
最短路徑不能包含權重為負值的環路。而且,最短路徑也不能包含權重為正值得環路。因為只要將環路從路徑上刪除就可以得到一條權重更小的路徑。
如果從s到節點v存在一條包含權重為0的環路的最短路徑,則將該環路刪除之後,就可以得到另外一條最短路徑。
所以,假定找到的最短路徑上沒有環路,即它們都是簡單路徑。由於圖G=(V, E)的任意無環路徑最多包含|V|個不同的節點,因而最多包含|V|-1條邊。
5:最短路徑的表示
除了需要計算最短路徑的權重,還需要計算最短路徑上的節點。給定圖G=(V, E),對於每個節點v,需要維持前驅節點v.π 。本章的最短路徑演算法將對每個節點的π屬性進行設定,給定節點v,如果v.π != NULL,則PRINT-PATH(G, s, v)就能打印出從節點s到v的一條最短路徑。
定義圖G 的前驅子圖 = ( ),其中 是圖G中的前驅節點不是NULL的節點的集合。同時,還包含源節點s,所以: = {v ∈ V;v.π != NULL} ∪ {s}。而 = {(v.π, v) ∈ E;v ∈ v-{s} }。
當演算法終止時,是一顆“最短路徑樹”,非形式化的說,最短路徑樹是一顆根節點為s的樹,該樹包括了從源節點s到每個可以從s到達的節點的一條最短路徑。更精確的定義如下:
設圖G=(V, E)是一條帶權重的有向圖,權重函式為w,並假定G中不包含從源點s可以到達的權重為負值的環路,因此所有的最短路徑都有定義。一顆根節點為s的最短路徑樹是一個有向子圖G’ = (V’ , E’),V’ , E’ E,滿足:
a:V’是圖G中從源節點s可以到達的所有節點的集合
b:G’形成一顆根節點為s的樹
c:對於所有的節點v ∈ V’,圖G’中從節點s到v的唯一簡單路徑是G中從節點s到v的一條最短路徑。
另外,最短路徑不是唯一的,最短路徑樹也不一定是唯一的。
6:鬆弛操作
每個節點v,維持一個屬性v.d,表示從源節點s到節點v的最短路徑權重的上屆。通過下面的執行時間為O(V)的演算法來對v.d和v.π 進行初始化:
INITIALIZE-SINGLE-SOURCE(G, s)
for each vertex v ∈ G.V
v.d = ∞
v.π = NIL
s.d = 0
下面的演算法就是對邊(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
如下圖就是對一條邊進行鬆弛的例子,第一個例子,v.d因鬆弛操作而減少了,第二個例子卻沒有變化:
本章中的每個演算法都會呼叫INITIALIZE-SINGLE-SOURCE,然後重複對邊進行鬆弛的過程。另外,鬆弛是改變最短路徑估計v.d和前趨v.π的唯一方式。本章中的演算法之間的區別在於對每條邊進行鬆弛操作的次數,以及對邊執行鬆弛操作的次序有所不同。
7:最短路徑和鬆弛操作的性質
a:三角不等式性質:對於任何邊(u,v) ∈ E,有 δ(s, v)<= δ(s, u) +w(u, v)。
b:上屆性質:對於所有的節點v∈ V,我們總有v.d >= δ(s, v)。一旦v.d的取值達到 δ(s, v),其值將不再發生變化。
c:非路徑性質:如果從節點s到節點v之間不存在路徑,則總有v.d = δ(s, v) = ∞。
d:收斂性質:對於某些節點u, v ∈V,如果s ~> u-> v是圖G中的一條最短路徑,並且對邊(u, v)進行鬆弛前的任意時間有u.d = δ(s, u),則在對邊(u, v)鬆弛之後的所有時間v.d =δ(s, v)。
e:路徑鬆弛性質:如果p=< >是從源節點s = 到的一條最短路徑,並且對p中的邊進行鬆弛的次序為( ), ( ), ..., (),則.d =δ(s, v)。該性質的成立與任何其他的鬆弛操作無關,即使這些鬆弛操作是與對p上的邊所進行的鬆弛操作穿插進行的。
f:前驅子圖性質:對於所有的節點v∈ V,一旦v.d =δ(s, v),則前驅子圖是一顆根節點為s的最短路徑樹。
注意:假定對於任意實數a != -∞,有a +∞ =∞ + a =∞ ,如果a !=∞ ,有a +(-∞) = (-∞)+ a =-∞ 。
本章所有的演算法都假設有向圖G用鄰接表的形式儲存。
二:Bellman-Ford演算法
Bellman-Ford演算法解決的是一般情況下的單源最短路徑問題,邊的權重可以為負值,同時,該演算法返回一個布林值,來表明是否存在一個從源節點可以到達的權重為負值的環路。如果存在負值環路,則演算法沒有解決方案,如果不存在,演算法給出最短路徑以及權重。
BELLMAN-FORD(G,w, s)
INITIALIZE-SINGLE-SOURCE(G, s)
for i = 1 to |G.V|-1
for each edge (u, v)∈ E
RELAX(u, v, w)
for each edge (u, v) ∈ E
if v.d > u.d + w(u, v)
return FALSE
return TRUE
該演算法返回TRUE表明圖中不包含從源節點可達的負值環路。演算法對圖中的每條邊進行|G.V|-1的處理,下圖為例項:
該演算法第一行的初始化需要O(V)的時間,第2-4行的迴圈時間為O(E),且一共需要|G.V|-1 次迴圈,第5-7行的時間為O(E),所以,Bellman-Ford演算法的總執行時間為O(VE)。
該演算法的正確性證明:在該演算法的2-4行的for迴圈中,每次迴圈對每條邊都進行鬆弛。在第i次迴圈中,被鬆弛的邊肯定包括( ),所以根據鬆弛定理得證正確。對於是否具有環路而返回TURE或FALSE,參見P380。
三:有向無環圖中的單源最短路徑問題
該演算法針對有向無環圖,根據結點的拓撲排序的次序來進行邊的鬆弛操作,可以在O(V+E)時間內計算出源節點到所有節點的最短路徑。有向無環圖中的邊權重可以為負值。
DAG-SHORTEST-PATHS(G,w, s)
topologically sort the vertices of G
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)
下圖描述了概演算法的一個例項:
該演算法第一行的拓撲排序時間為O(V+E),初始化時間為O(V),外迴圈針對每個結點,因此內迴圈針對每條邊,所以演算法的總執行時間為O(V+E)。該演算法之所以正確,就是因為拓撲排序 ,表明了存在邊( ),所以根據鬆弛定理可證。
四:Dijkstra演算法
該演算法要求所有邊的權重為非負值,如果採用合適的實現方式,Dijkstra演算法的執行時間要比Bellman-Ford演算法快。
該演算法維護集合S,從源節點s到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)
該演算法維持的不變式是Q=V-S,該演算法總是選擇Q中最近的節點加入到S中,使用的是貪心策略。下圖為演算法執行示例:
雖然貪心策略不一定能夠得到最優解,但是使用貪心策略的Dijkstra演算法確實能夠得到最優解,這是因為每次選擇節點u加入到集合S時,都有u.d = δ(s,u)。
該演算法執行三種優先佇列:INSERT, EXTRACT-MIN, DECREASE-KEY(隱含在RELAX中)。對每個節點呼叫一次INSERT和EXTRACT-MIN,最多對每條邊進行一次DECREASE-KEY操作。
如果對圖中的節點進行從1到|V|的編號,然後用|V|大小的陣列來維持優先佇列,將v.d存放在陣列的第v個索引中,則每次INSERT和DECREASE-KEY執行時間為O(1),每次EXTRACT-MIN的操作時間為O(V)。所以,演算法執行總時間為O()= O()
如果討論的是稀疏圖,則可以使用二叉堆,演算法總執行時間為O((V+E)lg V)。如果使用斐波那契堆實現最小佇列,則時間可以減少到O(Vlg V + E)。
五:差分約束和最短路徑
給定一個m*n的矩陣A,一個m維的向量b,希望找到一個n維向量x,滿足Ax<= b。也需要能夠分析不出存在這樣的解的情況。
這種問題的特例是差分約束系統,其中矩陣A的每一行包括一個1和一個-1,其他所有項為0。因此,Ax <= b所給出的約束條件變為m個涉及n個變數的差額限制條件,每個約束條件的形式為: <=。比如,下面的問題:
這個問題與尋找滿足下列8個差分約束條件的變數 , , , , 的問題等價:
這樣的問題答案有多個:如果向量x=( , )是差分約束系統Ax<=b的一個解,設d為任意常數,則x+d = (+d, )也是該差分約束系統的一個解。
差分約束系統可以用許多不同的應用:例如, 可以是事件將要發生的時刻,每個約束條件可以解釋為兩件事情之間必須間隔的最短時間或者最長時間。
可以從圖論的角度來解決差分約束系統,可以把m*n的矩陣A看做n個節點,m條邊的有向圖,圖中每個節點 對應於某個 ,每條邊則對應一個不等式。正式的說:
給定差分約束系統Ax <= b,其對應的約束圖是一個帶權重的有向圖G=(V, E),這裡:
約束圖中包含一個額外的節點 ,從其出發可以到達所有其他的節點。如果 <= 是一個差分約束條件,則邊(, )的權重為。所有從節點發出的邊權重為0,如下圖:
可以通過在約束圖中尋找最短路徑來得到差分約束系統的一個解,如果G不包含權重為負值的環路,則是一個可行解,如果G中包含負值環路,則該系統沒有可行解。
可以用Bellman-Ford演算法解決差分約束系統,如果該演算法返回TRUE,則最短路徑權重給出的是一個可行解,否則,表明沒有可行解。因為約束圖有n+1個節點和m+n條邊,所以演算法執行時間為O(),可以對該演算法進行優化使得時間為O(nm)。