最短路總結

2021-07-28 16:35:44 字數 4123 閱讀 1733

寫個部落格記錄一下最短路的幾種演算法,盡量做最正確的解答,減少大家的疑惑,網上有好多講的都抄來抄去,還有好多講的都是錯誤的。。。

熟悉的最短路演算法就幾種:bellman-ford,dijkstra,spfa,floyd,下面針對這幾個演算法具體解析一下。首先說明一點,就是關於負環的問題。

bellman-ford可以用於邊權為負的圖中,圖里有負環也可以,如果有負環,演算法會檢測出負環。

dijkstra只能用於邊權都為正的圖中。

spfa是個bellman-ford的優化演算法,本質是bellman-ford,所以適用性和bellman-ford一樣。

floyd可以用於有負權的圖中,即使有負環,演算法也可以檢測出來。

任何題目中都要注意的有四點事項:圖是有向圖還是無向圖、是否有負權邊,是否有重邊,頂點到自身的可達性。這幾點非常重要,可以在我下面的講解中體會。

這個最簡單,只能在邊權都為正的圖中用這個演算法,不論是有向圖還是無向圖。演算法是個貪心的過程,每次拿出乙個沒有被標記過的距離最小的頂點,並從這個點進行擴充套件,也就是嘗試鬆弛從這個點出發的每條邊。為什麼一定要用在正權圖中呢?因為演算法的過程相當於把整個圖中的點乙個乙個加入到「處理完」集合s的過程,並且處理完集合中的點的距離一定是從源點到該點的最小距離。如果圖中有負權,會導致乙個進入集合中的點可能在後面的過程中距離值變得更小,演算法就錯了。舉個例子來說:有向圖

1 2 2

1 3 3

3 2 -2

求點1到其他各個節點的最短路。

根據dijkstra演算法,首先會把節點1放入到集合s中,然後更新節點2和3的值,距離分別為d[2]=2,d[3]=3;之後因為節點2的d值比節點3的小,所以把節點2加入到s集合,然後嘗試鬆弛從節點2出發的邊,發現沒有可以更新的,演算法繼續;最後拿出節點3,但是此時有一條3到2的權值為-2的邊,這樣導致了節點2的距離值更小了,演算法所維護的集合s的特性被破壞了,演算法也就不正確了。所以dijkstra絕對不能用於有負權的圖。

這個要講很多,裡面也有我最糾結的思考。

首先說下演算法的用途,有向圖邊權可正可負,求源點到每個點最小距離。演算法就是執行了n-1次對所有邊的鬆弛而已。演算法基於這樣的事實,如果存在最短路,那麼最短路中一定沒有環。如果有0環,那麼把這個0環去掉不影響結果;如果是正權環,那麼去掉這個環路徑長度更小;如果是負權環,那麼最短路徑不存在,因為可以走無數次這個環,路徑長度會無窮小。為什麼執行n-1次迴圈就可以了,這個證明要看演算法導論,這裡我簡單寫一下:

設g=(v,e)是乙個源點為s的有向圖,權函式為w,假設g中不包含從源點可達的負權迴路,那麼演算法結束時(執行完n-1輪鬆弛操作),對任意節點v有d[v]=δ(s, v)

證明:對任意s可達的點v,總能找到一條最短路徑p=(v0, v1, ..., vk),其中v0=s,vk=v。因為最短路徑都是簡單路徑,那麼p至多包含n-1條邊,所以k<=n-1。由於v0=s,所以d[s]=δ(s,s)=0,當對所有的邊進行第一次鬆弛後,一定有d[v1]=δ(s,v1)。以此類推,對所有的邊進行第k次鬆弛後,有d[vk]=δ(s,vk)。因此當對所有邊進行n-1次鬆弛之後,必有d[v]=d[vk]=δ(s,vk)=δ(s,v)。然後我們證明為什麼當d[v(i-1)]=δ(s,v(i-1))時,對邊(v(i-1), vi)鬆弛之後有d[v(i)]=δ(s,v(i))。由於s->v1->...->vi是一條最短路徑,在對邊(v(i-1),vi)鬆弛之後一定有d[vi]<=d[v(i-1)]+w(v(i-1),vi)=δ(s,v(i-1))+w(v(i-1),vi)=δ(s,vi),又由於d[vi]>=δ(s,vi)(這個陣列d的性質),所以d[vi]=δ(s,vi)。這個是比較嚴謹的證明。 網上有這樣的描述性證明:演算法可以看成個動態規劃的過程,也即最多經過k條邊的最短路可以由最多經過k-1條邊的最短路再添一條邊獲得。具體來說就是,第一輪鬆弛代表從s經過最多一條邊的可以到達的所有點的最短距離,第二輪鬆弛代表從s經過最多兩條邊可以到達的所有點的最短距離,後續略。這樣的說法對不對?對!是可以這樣子想,但是d陣列的意義卻不是這樣。比如第一輪鬆弛之後陣列d中的值可不是源點可達的經過最多一條邊的最短距離,可能包括了經過多條邊的最短距離。這是因為我們一直是對d陣列進行操作的,在一輪的鬆弛中,我們用到了本輪之前的鬆弛操作得到的結果。所以要切記,第k輪操作之後結束的時候,d陣列中儲存的值並不是最多經過k條邊的最短路!!!那麼怎麼用這個動態規劃的思想呢?很簡單,只要用兩個陣列儲存記錄就行了。雖然按照網上的這種說法,d陣列的意義並不正確,但是最終得到正確的結果是沒有問題的。

另外容易忽視的乙個點是,演算法會求出從源點可達的負權環,如果乙個負權環從源點不可達,那麼演算法是求不出來的,不過可以用乙個非常小的技巧達到這一點,建立乙個超級源點向原圖中的每個頂點建立一條邊,邊權值任意,那麼從這個超級源點到原圖中的任意點都可達了,就能處理所有的負權環了。

有乙個非常糾結的點是,演算法隱含了乙個條件,一條有向邊可以用無數次。這個可能是非常容易忽略的地方,雖然很少有題目會涉及到,但是還是值得思考一下。像我在上面講的,如果存在乙個負權環,那麼最短路徑不存在,這就隱含了一條邊可以走無數次,那如果一條邊只能走一次呢?這種情況下所有的最短路演算法都不能用了,但是圖中任意點的最短路都會存在,怎麼求有待思考,我是不會做。。。我猜這個問題是npc的。此外,求解有向圖最短路徑是p的,最短簡單路徑和最長簡單路徑都是npc的(簡單路徑指頂點不能重複走)。如果乙個有向圖沒有負環(可以有負權邊),那麼這個圖的最短簡單路徑是p的;同理,如果乙個有向圖沒有正環,那麼這個圖的最長簡單路徑也是p的。

然後說我最糾結的地方,無向圖的最短路怎麼求。網上都這樣說,無向邊拆成正反兩條邊就行了,其實不是這樣的,要看具體的情況。比如給你個無向圖,讓你求最短路,那你要注意這個圖中是否有負權邊,如果沒有的話,無向邊拆成正反兩條邊是沒問題的,但是如果有負權邊並且規定一條無向路只能走一次,那拆邊就錯了,因為拆邊之後會認為圖中存在負環,最短路不存在,但是依照題意,最短路一定是存在的(列舉所有路徑即可),這個問題和上面的有向圖的有向邊最多只能用一次的最短路問題是同一類問題,目測也是npc的。很多圖論的問題、以及帶限制條件的最短路問題都是npc的。

以下這段話不是針對最短路說的,是針對無向圖說的。無向圖中只要有負權邊,那麼一定要從題意角度出發思考問題,沒有固定的演算法可用。比如兩個點一條負權邊組成的圖算不算含負圈?有些題算,因為這樣的定義可以使用很多現有的演算法,有些題不算,因為直觀上想,只有一條邊怎麼能算有圈?還有一些題對圈的特性做了一些限制,比如求的是簡單圈等等。無向圖中的trick有很多(考慮重邊,圈,頂點能否重用等等),列舉不完,做題目的時候要特別留心注意。

spfa就是個bellman-ford的優化,演算法本質和bellman-ford是一樣的,具體不多說。有個應用就是可以判負環,當乙個點進入佇列大於等於n次的時候,就可以認為圖中存在負環了,當然spfa能做的bellman-ford都能做,bellman-ford也可以判負環。

floyd演算法是非常強大的,可以處理不少問題,複雜度是o(n^3)的,下面解析一下這個演算法

不少人可能剛接觸floyd的時候非常容易把它寫錯,錯誤的寫法就是三層迴圈的從外到內的變數分別為i,j,k,正確的寫法應該是k,i,j。寫錯的原因是不理解floyd演算法造成的,那麼為什麼從順序是k,i,j呢?

其實floyd的演算法本質是個動態規劃!dp[k][i][j]代表i到j的中間節點(不包括i和j)都在區間[1,k]時,i到j的最短路。演算法的最外層迴圈是個從小到大列舉k的過程,當最外層剛剛進入第k次迴圈的時候,我們已經得到了所有點對的dp[k-1]的值,也就是所有點對(i,j)的i到j的中間節點都在[1,k-1]區間的i到j的最短路。那麼對任意的點對(i,j),如果他倆的最短路經過k點,則dp[k][i][j]=dp[k-1][i][k]+dp[k-1][k][j];如果不經過k點,則dp[k][i][j]=dp[k-1][i][j]。所以當我們求dp[k]的時候,要保證所有的dp[i-1]都求出來了,因此,最外層迴圈是k。

floyd能做很多事情,下面簡單說兩個,求有向圖的最小環或者最大環(頂點數》=2),求無向圖的最小環(頂點數》=3)。

先說求有向圖最小環(最大環略)。有兩種方法可以求,一種是設定g[i][i]為無窮大,這樣最後找所有的g[i][i]裡的最小值就行;另一種是正常做floyd,然後對每個點對(i,j),求g[i][j]+dp[n][j][i]的最小值,這樣的原理是最小環如果存在的話,那麼可以列舉乙個這個環裡的邊i->j,那麼包含這條邊的最小的環一定是i->j和dp[n][j][i]構成的最短路。

無向圖的最小環做法和有向圖不一樣,是因為無向邊可能會被用兩次導致出錯,舉例說就是:列舉了一條邊i->j,然後其與dp[n][j][i]的和作為乙個結果,但是如果j到i的最短路就是邊j->i的話,那麼我們找的環其實只是一條邊而已,這樣的結果顯然是錯誤的。正確的做法是先判斷最小環再更新最短路

最短路總結

穿越空間的限制,走最短的路找到你 u v之間的最短路滿足以下限制 對任意k g v,e 有 dist u,v dis u,k dis k,j 關鍵操作 鬆弛 void relax int i,int j,int k floyd void floyd 複雜度o v 3 可處理負環 拓展把所有邊存成負的...

最短路總結

首先是dij演算法,這是我第乙個掌握的最短路演算法!再來是bellman ford演算法,這個演算法比較容易理解,而且考慮到了負環的存在。記住,它對圖中的邊進行了 v 1次操作!首先,對d進行初始化 還有乙個spfa演算法 摘錄於學長空間 設立乙個先進先出的佇列用來儲存待優化的結點,優化時每次取出隊...

最短路總結

本來以為自己不用總結 回頭再看這幾種演算法都忘光了qaq 如果存在乙個環 從某個點出發又回到自己的路徑 而且這個環上所有權值之和是負數,那這就是乙個負權環,也叫負權迴路 floyd演算法適合解決多源最短路 複雜度最高 解決負邊 負權 但不能解決負環 void floyd floyd 最後map i ...