(floyd)佛洛伊德演算法

2022-03-22 15:38:53 字數 3557 閱讀 8388

floyd–warshall(簡稱floyd演算法)是一種著名的解決任意兩點間的最短路徑(all paris shortest paths,apsp)的演算法。從表面上粗看,floyd演算法是乙個非常簡單的三重迴圈,而且純粹的floyd演算法的迴圈體內的語句也十分簡潔。我認為,正是由於「floyd演算法是一種動態規劃(dynamic programming)演算法」的本質,才導致了floyd演算法如此精妙。因此,這裡我將從floyd演算法的狀態定義、動態轉移方程以及滾動陣列等重要方面,來簡單剖析一下圖論中這一重要的基於動態規劃的演算法——floyd演算法。

在動態規劃演算法中,處於首要位置、且也是核心理念之一的就是狀態的定義。在這裡,把d[k][i][j]定義成:

「只能使用第1號到第k號點作為中間媒介時,點i到點j之間的最短路徑長度。」

圖中共有n個點,標號從1開始到n。因此,在這裡,k可以認為是動態規劃演算法在進行時的一種層次,或者稱為「鬆弛操作」。d[1][i][j]表示只使用1號點作為中間媒介時,點i到點j之間的最短路徑長度;d[2][i][j]表示使用1號點到2號點中的所有點作為中間媒介時,點i到點j之間的最短路徑長度;d[n-1][i][j]表示使用1號點到(n-1)號點中的所有點作為中間媒介時,點i到點j之間的最短路徑長度d[n][i][j]表示使用1號到n號點時,點i到點j之間的最短路徑長度。有了狀態的定義之後,就可以根據動態規劃思想來構建動態轉移方程。

動態轉移的基本思想可以認為是建立起某一狀態之前狀態的一種轉移表示。按照前面的定義,d[k][i][j]是一種使用1號到k號點的狀態,可以想辦法把這個狀態通過動態轉移,規約到使用1號到(k-1)號的狀態,即d[k-1][i][j]。對於d[k][i][j](即使用1號到k號點中的所有點作為中間媒介時,i和j之間的最短路徑),可以分為兩種情況:(1)i到j的最短路不經過k;(2)i到j的最短路經過了k。不經過點k的最短路情況下,d[k][i][j]=d[k-1][i][j]。經過點k的最短路情況下,d[k][i][j]=d[k-1][i][k]+d[k-1][k][j]。因此,綜合上述兩種情況,便可以得到floyd演算法的動態轉移方程:

d[k][i][j] = min(d[k-1][i][j], d[k-1][i][k]+d[k-1][k][j])(k,i,j∈[1,n])

最後,d[n][i][j]就是所要求的圖中所有的兩點之間的最短路徑的長度。在這裡,需要注意上述動態轉移方程的初始(邊界)條件,即d[0][i][j]=w(i, j),也就是說在不使用任何點的情況下(「鬆弛操作」的最初),兩點之間最短路徑的長度就是兩點之間邊的權值(若兩點之間沒有邊,則權值為inf,且我比較偏向在floyd演算法中把圖用鄰接矩陣的資料結構來表示,因為便於操作)。當然,還有d[i][i]=0(i∈[1,n])。

這樣我們就可以編寫出最為初步的floyd演算法**:12

3456

78910

1112

voidfloyd_original()

}

}

}

幾乎所有介紹動態規劃中最為著名的「0/1揹包」問題的演算法書籍中,都會進一步介紹利用滾動陣列的技巧來進一步減少演算法的空間複雜度,使得0/1揹包只需要使用一維陣列就可以求得最優解。而在各種資料中,最為常見的floyd演算法也都是用了二維陣列來表示狀態。那麼,在floyd演算法中,是如何運用滾動陣列的呢?

再次觀察動態轉移方程d[k][i][j] = min(d[k-1][i][j], d[k-1][i][k]+d[k-1][k][j]),可以發現每乙個第k階段的狀態(d[k][i][j]),所依賴的都是前一階段(即第k-1階段)的狀態(如d[k-1][i][j],d[k-1][i][k]和d[k-1][k][j])。

上圖描述了在前面最初試的floyd演算法中,計算狀態d[k][i][j]時,d[k-1]和d[k]這兩個二維陣列的情況(d[k-1]表示第k-1階段時,圖中兩點之間最短路徑長度的二維矩陣;d[k]表示第k階段時,圖中兩點之間最短路徑長度的二維矩陣)。紅色帶有箭頭的有向線段指示了規劃方向。灰色表示已經算過的陣列元素,白色代表還未算過的元素。由於d[k-1]和d[k]是兩個相互獨立的二維陣列,因此利用d[k-1][i][j],d[k-1][i][k]和d[k-1][k][j](皆處於上方的二維陣列中)來計算d[k][i][j]時沒有任何問題。

那如何利用乙個二維陣列來實現滾動陣列,以減小空間複雜度呢?

上圖是使用滾動陣列,在第k階段,計算d[i][j]時的情況。此時,由於使用d這個二維陣列作為滾動陣列,在各個階段的計算中被重複使用,因此陣列中表示階段的那一維也被取消了。在這圖中,白色的格仔,代表最新被計算過的元素(即第k階段的新值),而灰色的格仔中的元素值,其實儲存的還是上一階段(即第k-1階段)的舊值。因此,在新的d[i][j]還未被計算出來時,d[i][j]中儲存的值其實就對應之前沒有用滾動陣列時d[k-1][i][j]的值。此時,動態轉移方程在隱藏掉階段索引後就變為:

d[i][j] = min(d[i][j], d[i][k]+d[k][j])

(k,i,j∈[1,n])

賦值號左側d[i][j]就是我們要計算的第k階段是i和j之間的最短路徑長度。在這裡,需要確保賦值號右側的d[i][j], d[i][k]和d[k][j]的值是上一階段(k-1階段)的值。前面已經分析過了,在新的d[i][j]算出之前,d[i][j]元素保留的值的確就是上一階段的舊值。但至於d[i][k]和d[k][j]呢?我們無法確定這兩個元素是落在白色區域(新值)還是灰色區域(舊值)。好在有這樣一條重要的性質,dp[k-1][i][k]和dp[k-1][k][j]是不會在第k階段改變大小的。也就是說,凡是和k節點相連的邊,在第k階段的值都不會變。如何簡單證明呢?我們可以把j=k代入之前的d[k][i][j]=min(d[k-1][i][j], d[k-1][i][k]+d[k-1][k][j])方程中,即:

d[k][i][k]

= min(d[k-1][i][k], d[k-1][i][k]+d[k-1][k][k])

= min(d[k-1][i][k], d[k-1][i][k]+0)

= d[k-1][i][k]

也就是說在第k-1階段和第k階段,點i和點k之間的最短路徑長度是不變的。相同可以證明,在這兩個階段中,點k和點j之間的的最短路徑長度也是不變的。因此,對於使用滾動陣列的轉移方程d[i][j] = min(d[i][j], d[i][k]+d[k][j])來說,賦值號右側的d[i][j], d[i][k]和d[k][j]的值都是上一階段(k-1階段)的值,可以放心地被用來計算第k階段時d[i][j]的值。

利用滾動陣列改寫後的floyd演算法**如下:12

3456

voidfloyd()

因此,通過這篇文章的分析,我們可以發現,floyd演算法的的確確是一種典型的動態規劃演算法;理解floyd演算法,也可以幫助我們進一步理解動態規劃思想。

Floyd佛洛伊德演算法

弗洛伊德演算法求解圖中任意一對頂點之間的最短路徑,其路徑資訊用二維陣列path 儲存,另外還需維護乙個二位陣列a k i j 用來儲存頂點i經由頂點k作為中間頂點到達頂點j的最短路徑長度,當然如果以k作為中間頂點時路徑較之前邊長,則陣列a中的路徑長度仍不變,與其對應的path陣列也不會將k存入對應位...

弗洛伊德(Floyd)演算法

弗洛伊德 floyd 演算法過程 用d v w 記錄每一對頂點的最短距離。依次掃瞄每乙個點,並以其為基點再遍歷所有每一對頂點d的值,看看是否可用過該基點讓這對頂點間的距離更小。演算法理解 最短距離有三種情況 兩點的直達距離最短。如下圖 兩點間只通過乙個中間點而距離最短。圖 兩點間用通過兩各以上的頂點...

弗洛伊德 Floyd 演算法

和dijkstra演算法一樣,弗洛伊德 floyd 演算法也是一種用於尋找給定的加權圖中頂點間最短路徑的演算法。該演算法名稱以創始人之一 1978年圖靈獎獲得者 史丹福大學電腦科學系教授羅伯特 弗洛伊德命名 弗洛伊德演算法 floyd 計算圖中各個頂點之間的最短路徑 迪傑斯特拉演算法用於計算圖中某乙...