單源最短路徑(Bellman-Ford算法)


單源最短路徑和廣度優先搜索要做的事很像。

關於廣度優先搜索可以看圖算法這一篇筆記。

單源最短路徑給定一個源s,當算法執行完畢,找出從源s到圖中的每個頂點權重最小的一條路徑。

其實廣度優先搜索可以看作特殊情況的單源最短路徑,在廣度優先搜索解決的圖中,所有的邊權重都為1。

注意: 本篇筆記說的最短路徑都是指權重最低的路徑,並非通過的邊數最少的路徑

負權重

某些單源最短路徑問題的算法中允許出現負權重的邊,就比如我們要討論的Bellman-Ford算法。

但是有些算法不允許,比如Dijkstra算法。

關於環路

單源最短路徑問題中能不能存在環路呢?我們分情況討論。

  1. 權值為正的環路
    我們看s到c的路徑,這個路徑包含一個環路,即節點c和節點d。我們從s出發,到c之后還可以走無數次c和d構成的環路,最終都能回到c。但是由於我們每從c走到d,權重就會加6,再從d走回c會減3,所以每次繞環是需要額外花費3的權重的。所以我們最終的找出的最短路徑中不可能存在權值為正的環路。
  2. 權值為負的環路
    看s到e的路徑,和上面的分析一樣,每次繞e和f組成的環,會減去3的權值,所以這種情況已經不存在最短(權重最低)路徑了,因為每次繞環權值都會更小,所以最短的路徑應該是負無窮。這種情況算法得不到正確的答案。我們的Bellman-Ford算法會檢測到負值環,並返回False通知調用者。
  3. 權值為零的環路
    這種情況無論繞環走多少次都無影響,所以我們可以重復刪除這些環路,最后得到一條不包括環路的最短路徑。

綜上所述,算法最后找到的最短路徑中應該不包含環路,即都是簡單路徑。

松弛操作

我們來看下單源最短路徑算法的核心操作。松弛。

對於每個節點v,維持一個屬性v.d,這個屬性用來維持源s到v的最短路徑估值。它是s到v的一條最短路徑權重的上界。

對於每個節點v,維持一個屬性v.PI,這個屬性和d配合,用於記錄當前的估值情況下和v連接的前驅節點。

提供一個權重函數w(u,v)計算u和v構成的邊的權重

我們給每個節點的d和PI初始值定為無窮和NIL。把源節點s的d定為0

INITIALIZE-SINGLE-SOURCE(G,s)
    for 圖G中的每個節點 v
        v.d = ∞
        v.PI = NIL
    s.d = 0

提供松弛函數RELAX(u,v,w)對邊(u,v)進行松弛,參數w是權重函數。

松弛函數其實就是測一下如果經過u的話會不會對節點v的最短路徑估值d起到優化。在說通俗點就是測試連接(u,v)的話會不會讓當前s到v的最短路徑估值更小。如果是就連接(u,v)

RELAX(u,v,w)
    if v.d > u.d + w(u,v)
        v.d = u.d + w(u,v)
        v.PI = u

對溜,就是這樣,如果當前節點v的估值大於u的估值加邊(u,v)的權重的話就重置v的估值為u.d+w(u,v)並設置v的前驅節點為u。

最短路徑和松弛操作性質

我們假設sp(s,v)是從源s到節點v的最短路徑的權重

  1. 三角不等式性質: 對於任何邊(u,v)我們有sp(s,v) <= sp(s,u) + w(u,v)
    啥意思呢,就是說明對於每個邊,要么就是最短路徑里需要走這條邊,這時不等式中的小於號拿掉,變成等式。要么就是可以有一條不經過`(u,v)的更短的路徑通往v。
  2. 上界性質:對於所有節點v,總有v.d >= sp(s,v)
    這是一條關於松弛操作的性質,我們不斷調用松弛操作去調節節點v的最短路徑估值d,一旦發現更短的路徑就更新它,直到v.d == sp(s,v)后,松弛操作將永遠找不到更短的路徑,v.d就永遠不會更新了。
  3. 非路徑性質:如果s到v之間不存在路徑,則v.d == sp(s,v) == ∞
    很正常,如果不存在路徑,那么v的估值永遠得不到更新,一直是無窮。
  4. 收斂性質:對於某些節點u,v,如果s⇝u→v是圖中的一條最短路徑,並且在對邊(u,v)進行松弛前的任意時間有u.d == sp(s,u),則在之后的所有時間有v.d == sp(s,v)
  5. 路徑松弛性質:如果p=(v0,v1,...,vk)是從源節點s=v0到節點vk的一條最短路徑,並且對p中邊的松弛次序為(v0,v1),(v1,v2)...(vk-1,vk),則vk.d == sp(s,vk)。該性質的成立與其他任何松弛操作無關,即使這些松弛操作是和對p上的邊所進行的松弛操作穿插進行的。
    因為p是最短路徑,所以每次按上面順序進行松弛估值一定會更新到最短的路徑啊。毋庸置疑。這條和第四條差不多啊。
  6. 前驅子圖性質:如對於所有節點v,一旦v.d == sp(s,v)則前驅子圖是一顆以跟節點為s的最短路徑樹。
    這性質在“關於環路”那個段落里其實已經隱含了,因為最短路徑一定是不包含環路的簡單路徑,所以肯定是一棵樹。

不好意思題外話。。。🐎了個🥚,抄書好累啊,但是我不得不把算法導論翻譯成人話,沒錯我在逼自己看懂它。。。

有了上面的分析,我們可以開始研究主角Bellman-Ford算法了

Bellman-Ford算法

Bellman-Ford算法可以工作在不存在從源節點可以達到的權重為負值的環路的圖。

如果存在這樣的圖,算法將返回False通知用戶無法給出正確可靠的結果。

算法通過不斷對每條邊進行松弛來一點點的使每個節點v的最短路徑估值d一點一點的下降,最終達到sp(s,v)

其實對於每一次對所有邊挨個進行松弛操作,必定會有至少一條邊的d值會被設置為正確的。這樣只需要對圖的每條邊進行圖中節點數減1次循環就可以計算出每個節點正確的最短路徑估值。所以我們這樣編寫BELLMAN-FORD算法:

BELLMAN-FORD(G,w,s)
    INITIALIZE-SINGLE-SOURCE(G,s)
    for i = 1 to |G.V| - 1
        for G中每個邊 (u,v)
            RELAX(u,v,w)
    for G中每個邊 (u,v)
        if v.d > u.d + w(u,v)
            return FALSE
    return TRUE

But,,why???下面證明下。

算法能得到正確估值的證明

假設圖不包含從源節點s可以到達的權重為負值的環路,我們考慮每一個可以從源到達的節點v肯定有一條最短路徑p={v1,v2,...,vk}。s=v1,v=vk。由於我們前面的前驅子圖性質,所以這個最短路徑肯定是一個簡單路徑,即沒有環。那么這條路徑里就最多有圖中節點數量-1個邊。再根據前面的路徑松弛性質,只要這條最短路徑是按順序松弛的,那么就可以得到正確的d值,和其它松弛操作的順序無關,那么每次外層for循環都松弛所有的邊,那么在第i次松弛操作時被松弛的邊肯定包含(vi-1,vi),所以,v.d==vk.d==sp(s,vk)==sp(s,v)d就是對的嘍。

迄今為止,我們還有一個坑沒有解決。那就是算法的返回值。

算法是否會在存在負權環路時返回FALSE,在正常情況下返回TRUE呢?

算法能得到正確返回值的證明

先看看算法導論的證明,確實很嚴謹很正確。不過我下次復習看這個筆記的時候。。。我這笨腦袋肯定還得想半天。

所以得想想怎么說才能讓我下次一下就能懂。

emmm。。。看這。。。。圖???假設有這么一個負權環,從上面的節點開始,假設初始狀態那個頂上的節點的d是0,在第一圈到左下腳的節點時,還是滿足v.d<=u.d+w(u,v)的。不會返回FALSE,不過當它轉了一圈后再來就有問題了。會產生v.d>u.d+w(u,v)。而且隨着圈越轉越多這個差值會越來越離譜。

每次遍歷所有邊就必定要走一遍環路。所以到最后只需檢查v.d>u.d+w(u,v)即可。算法是正確的

參考資料


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM