spfa演算法是求解單源最短路徑問題的一種演算法,由理查德·貝爾曼(richard bellman) 和 萊斯特·福特創立的。有時候這種演算法也被稱為 moore-bellman-ford 演算法,因為 edward f. moore 也為這個演算法的發展做出了貢獻。它的原理是對圖進行v-1次鬆弛操作,得到所有可能的最短路徑。其優於迪科斯徹演算法的方面是邊的權值可以為負數、實現簡單,缺點是時間複雜度過高,高達 o(ve)。但演算法可以進行若干種優化,提高了效率。
演算法的思路:我們用陣列dis記錄每個結點的最短路徑估計值,用鄰接表或鄰接矩陣來儲存圖g。我們採取的方法是動態逼近法:設立乙個先進先出的佇列用來儲存待優化的結點,優化時每次取出隊首結點u,並且用u點當前的最短路徑估計值對離開u點所指向的結點v進行鬆弛操作,如果v點的最短路徑估計值有所調整,且v點不在當前的佇列中,就將v點放入隊尾。這樣不斷從佇列中取出結點來進行鬆弛操作,直至佇列空為止。
我們要知道帶有負環的圖是沒有最短路徑的,所以我們在執行演算法的時候,要判斷圖是否帶有負環,方法有兩種:
1.開始演算法前,呼叫拓撲排序進行判斷(一般不採用,浪費時間)
2.如果某個點進入佇列的次數超過n次則存在負環(n為圖的頂點數)
spfa演算法手動操作過程
下面我們採用spfa演算法對下圖求v1到各個頂點的最短路徑,通過手動的方式來模擬spfa每個步驟的過程
初始化:
首先我們先初始化陣列dis如下圖所示:(除了起點賦值為0外,其他頂點的對應的dis的值都賦予無窮大,這樣有利於後續的鬆弛)
此時,我們還要把v1如佇列:
現在進入迴圈,直到隊列為空才退出迴圈。
第一次迴圈:
首先,隊首元素出佇列,即是v1出佇列,然後,對以v1為弧尾的邊對應的弧頭頂點進行鬆弛操作,可以發現v1到v3,v5,v6三個頂點的最短路徑變短了,更新dis陣列的值,得到如下結果:
我們發現v3,v5,v6都被鬆弛了,而且不在佇列中,所以要他們都加入到佇列中:
第二次迴圈
此時,隊首元素為v3,v3出佇列,然後,對以v3為弧尾的邊對應的弧頭頂點進行鬆弛操作,可以發現v1到v4的邊,經過v3鬆弛變短了,所以更新dis陣列,得到如下結果:
此時只有v4對應的值被更新了,而且v4不在佇列中,則把它加入到佇列中:
第三次迴圈
此時,隊首元素為v5,v5出佇列,然後,對以v5為弧尾的邊對應的弧頭頂點進行鬆弛操作,發現v1到v4和v6的最短路徑,經過v5的鬆弛都變短了,更新dis的陣列,得到如下結果:
我們發現v4、v6對應的值都被更新了,但是他們都在佇列中了,所以不用對佇列做任何操作。佇列值為:
第四次迴圈此時,隊首元素為v6,v6出佇列,然後,對以v6為弧尾的邊對應的弧頭頂點進行鬆弛操作,發現v6出度為0,所以我們不用對dis陣列做任何操作,其結果和上圖一樣,佇列同樣不用做任何操作,它的值為:
spfa演算法手動操作過程
第五次迴圈此時,隊首元素為v4,v4出佇列,然後,對以v4為弧尾的邊對應的弧頭頂點進行鬆弛操作,可以發現v1到v6的最短路徑,經過v4鬆弛變短了,所以更新dis陣列,得到如下結果:
因為我修改了v6對應的值,而且v6也不在佇列中,所以我們把v6加入佇列,
第六次迴圈
此時,隊首元素為v6,v6出佇列,然後,對以v6為弧尾的邊對應的弧頭頂點進行鬆弛操作,發現v6出度為0,所以我們不用對dis陣列做任何操作,其結果和上圖一樣,佇列同樣不用做任何操作。所以此時隊列為空。ok,佇列迴圈結果,此時我們也得到了v1到各個頂點的最短路徑的值了,它就是dis陣列各個頂點對應的值,如下圖:
spfa演算法問題
即使兩個圖的節點和邊數完全一樣,僅僅是幾條邊的權值不同,他們的 spfa 佇列也會差距很大,如此感性上理解,spfa 是不穩定的。
眾所周知 spfa 可以看做是 bellman-ford 的佇列優化。 bellman-ford 每輪鬆弛會使最短路的邊數至少 +1,而最短路的邊數最多為 n−1,則其複雜度上界是穩定的 o(nm) 的。
但是 spfa 能被卡,能被卡到 bellman-ford 的複雜度,我們先講原因,再講方法:
究其原因,要從 spfa 是 bellman-ford 的優化說起。在 n 個點 m 條邊的圖中,bellman-ford 的複雜度是 n×m,依次對每條邊進行鬆弛操作,重複這個操作 n-1 次後則一定得到最短路,如果還能繼續鬆弛,則有負環。這是因為最長的沒有環路的路,也只不過是 n 個點 n-1 條邊構成的,所以鬆弛 n-1 次一定能得到最短路。
spfa 的意義在於,如果乙個點上次沒有被鬆弛過,那麼下次就不會從這個點開始鬆弛。每次把被鬆弛過的點加入到佇列中,就可以忽略掉沒有被鬆弛過的點。
但是最外層的迴圈還是 n-1 次。如果把被鬆弛的點放到前邊,他相當於沒有進行完這一輪鬆弛,就開始了一些其他的操作。但是這些其他的操作可能是無用的,因為這些操作的起始點可能還會被這一輪鬆弛更新。
所以傳統的 spfa 的複雜度不會超過n×m,並且每個點都不會第 n 次入隊。
漸進意義上,他的複雜度依然是 o(nm),一共只有 m 條邊所以每個節點最多隻會入隊 n 次。spfa 在本質上只是改變的入隊的順序,其複雜度上界依然是 o(nm),接下來介紹卡法
普通 spfa:
鏈套菊花,可以讓這個菊花節點反覆被入隊,造成時間的大量浪費。或者我們可以構造乙個具有大量次短路的網格圖,使得 spfa 容易一錯到底,即「在網格圖中走錯一次路可能導致很高的額外開銷」。我們可以考慮構造乙個網格圖套菊花,或者把兩種方法結合起來,放在同乙個 subtask 中。
漸進意義上,他的複雜度依然是 o(nm),一共只有 m 條邊所以每個節點最多隻會入隊 n 次。spfa 在本質上只是改變的入隊的順序,其複雜度上界依然是 o(nm),接下來介紹卡法
鏈套菊花,可以讓這個菊花節點反覆被入隊,造成時間的大量浪費。或者我們可以構造乙個具有大量次短路的網格圖,使得 spfa 容易一錯到底,即「在網格圖中走錯一次路可能導致很高的額外開銷」。我們可以考慮構造乙個網格圖套菊花,或者把兩種方法結合起來,放在同乙個 subtask 中。
lll 優化:
方法是比較入隊的邊權與 **e(平均值),如果更大則插入到隊尾。卡死的方法是向 1 連線一條權值極大的邊。
slf 優化:
極其廣為人知的優化,每次將入隊結點距離和隊首比較,如果更大則插入至隊尾。依然可以用鏈套菊花的方式卡,「鏈上用幾個並列在一起的小邊權」。帶容錯的版本依然可以通過高邊權和的方式卡死。
spfa演算法其它優化
除了佇列優化(spfa)之外,bellman-ford 還有其他形式的優化,這些優化在部分圖上效果明顯,但在某些特殊圖上,最壞複雜度可能達到指數級。
堆優化:將佇列換成堆,與 dijkstra 的區別是允許乙個點多次入隊。在有負權邊的圖可能被卡成指數級複雜度。
棧優化:將佇列換成棧(即將原來的 bfs 過程變成 dfs),在尋找負環時可能具有更高效率,但最壞時間複雜度仍然為指數級。
lll 優化:將普通佇列換成雙端佇列,每次將入隊結點距離和隊內距離平均值比較,如果更大則插入至隊尾,否則插入隊首。
slf 優化:將普通佇列換成雙端佇列,每次將入隊結點距離和隊首比較,如果更大則插入至隊尾,否則插入隊首。
d´esopo-pape 演算法:將普通佇列換成雙端佇列,如果乙個節點之前沒有入隊,則將其插入隊尾,否則插入隊首。
Bellman Ford演算法,SPFA演算法
bellman ford 演算法能在更普遍的情況下 存在負權邊 解決單源點最短路徑問題。對於給定的帶權 有向或無向 圖g v,e 其源點為 s,加權函式w是 邊集e 的對映。對圖g執行 bellman ford 演算法的結果是乙個布林值,表明圖中是否存在著乙個從源點s 可達的負權迴路。若不存在這樣的...
Spfa演算法模版
const maxn 5000 type link node node record x,dis longint next link end var g array 1.maxn of link dist,q array 1.maxn of longint v array 1.maxn of boo...
SPFA演算法模板
spfa shortest path faster algorithm 佇列優化 演算法是求單源 最短路徑 的一種演算法,在 bellman ford 演算法的基礎上加上乙個佇列優化,減少了冗餘的 鬆弛操作 是一種高效的最短路演算法。求單源最短路的spfa演算法的全稱是 shortest path ...