1. 程式人生 > >圖論動態規劃演算法——Floyd最短路徑

圖論動態規劃演算法——Floyd最短路徑

前言

推出一個新系列,《看圖輕鬆理解資料結構和演算法》,主要使用圖片來描述常見的資料結構和演算法,輕鬆閱讀並理解掌握。本系列包括各種堆、各種佇列、各種列表、各種樹、各種圖、各種排序等等幾十篇的樣子。

Floyd演算法

Floyd是一種經典的多源最短路徑演算法,它通過動態規劃的思想來尋找給定加權圖中的多源點之間的最短路徑,演算法時間複雜度是O(n3)。之所以叫Floyd是因為該演算法發明人之一是Robert Floyd,他是1978年圖靈獎獲得者,同時也是斯坦福大學計算機科學系教授。

image

核心思想

對於n個點的加權圖,從加權圖的權值矩陣開始,遞迴地更新n次最終求得每兩點之間的最短路徑矩陣。即加權圖的鄰接矩陣為D=(d_{ij})_{n \times n}

,按一定公式對該矩陣進行遞迴更新,初始時矩陣為D(0),第一次,根據公式用D(0)構建出矩陣D(1);第二次,根據公式用D(1)構建出矩陣D(2);以此類推,根據公式用D(n-1)構造出矩陣D(n),D(n)即為圖的距離矩陣,i行j列表示頂點i到頂點j的最短距離。

動態規劃

如何理解這種動態規劃演算法呢?可以理解為逐一選擇中轉點,然後針對該中轉點,所有以此為中轉點的其它點都要根據規定進行更新,這個規定就是原來兩點之間的距離如果通過該中轉點變小了則更新距離矩陣。比如選擇“1”作為中轉點,原來“02”之間的距離為5,通過中轉點1後(即路徑變為“012”)距離為4,變小了,那麼就更新距離矩陣對應的元素,由5更新為4。當圖中所有頂點都被作為中轉點處理以後,那麼得到的最後距離矩陣就是多源最短距離矩陣了。

c(i,j,k)為i到j的中間節點編號不超過k的最短距離,當k=0時,c(i,j,0)=d_{ij},對於n個頂點的圖,我們要求的i到j的最短距離即是c(i,j,n-1)

現在我們建立c(i,j,k)c(i,j,k-1)之間的遞迴關係,對於任意k,c(i,j,k)=min(c(i,j,k-1),c(i,k,k-1)+c(k,j,k-1)),於是可以根據該遞迴關係得到最終的最短路徑,即中間節點編號不超過n-1的最短距離。

演算法過程

  1. 從任意一條單邊路徑開始,所有兩點之間的距離是邊的權重,如果兩點之間沒有邊相連,則權重為無窮大。即用陣列D=(d_{ij})_{n \times n}來記錄i,j之間的最短距離,初始時,若i=j則dis[i][j]=0。若i、j兩點之間相連則dis[i][j]的值為該邊的權值。若i、j兩點沒有相連則dis[i][j]的值為無窮。
  2. 對於每一對頂點u和v,看看是否存在一個頂點w使得從u到w再到v比已知的路徑更短,如果存在則更新。即對所有的k值(1<k<n),計算(d[i][k]+d[k][j])的值,若其小於d[i][j],則d[i][j]=d[i][k]+d[k][j],否則d[i][j]的值不變。

神奇的五行程式碼

Floyd演算法核心就是下列五行程式碼,可以體會一下,三個for迴圈巢狀,最外層的k即是迴圈取不同中轉點時的情況,分別讓圖中每個頂點作為中轉點去更新距離,完成所有迴圈後就是最終的最短距離矩陣。

for(k=1;k<=n;k++)
    for(i=1;i<=n;i++)
        for(j=1;j<=n;j++) 
            if(d[i][k]+d[k][j]<d[i][j]) 
                    d[i][j]=d[i][k]+d[k][j];
複製程式碼

優缺點

優點:容易理解,可以算出任意兩個節點之間的最短距離,程式碼編寫簡單。對於稠密圖效果最佳,邊權可正可負。

缺點:時間複雜度比較高,不適合計算大量資料。

執行過程

對於一個擁有7個頂點的無向加權圖,分別用0-6來表示圖的每個頂點,每條邊上的數字為對應的權重。

image

首先根據每條邊的權重來初始化距離矩陣,這是一個[7x7]的矩陣,行列分別對應兩個頂點,比如第0行第1列表示頂點0到頂點1,對應的值為3,即權值為3。以此類推,其它元素分別代表對應邊的權重。

image

當以頂點0為中轉點時

逐一查詢看是否有以0為中轉點使得距離更短,實際上並不用比較所有矩陣的元素,只需比較滿足if (i != j && j != k && i != k)條件的元素,即都為0的對角線和中轉點對應的行列不用比較,因為對角線上的元素表示頂點自己到自己的距離,所以無需比較,而中轉點對應的行列表示頂點到中轉點的距離,也無需比較。

比較d[1][2]d[1][0]+d[0][2],因為1<3+5,所以不更新d[1][2]

image

往下,比較d[1][3]d[1][0]+d[0][3],因為4<3+INF,所以不更新d[1][3]

image

往下,比較d[1][4]d[1][0]+d[0][4],因為INF<3+INF,所以不更新d[1][4]。接著往下d[1][5]d[1][6]兩個元素情況類似。

image

比較d[2][1]d[2][0]+d[0][1],因為1<3+5,所以不更新d[2][1]

image

往下,比較d[2][3]d[2][0]+d[0][3],因為4<5+INF,所以不更新d[2][3]

image

往下,比較d[2][4]d[2][0]+d[0][4],因為8<5+INF,所以不更新d[2][4]

image

往下,比較d[2][5]d[2][0]+d[0][5],因為2<5+INF,所以不更新d[2][5]

image

往下,比較d[2][6]d[2][0]+d[0][6],因為INF<5+INF,所以不更新d[2][6]

image

比較d[3][1]d[3][0]+d[0][1],因為4<INF+3,所以不更新d[3][1]

image

類似地,剩下的元素全部都不更新,最後比較完d[6][5]d[6][0]+d[0][5]後即完成了以0作為中轉點的全部比較工作。

image

當以頂點1為中轉點時

逐一查詢看是否有以1為中轉點使得距離更短,比較d[0][2]d[0][1]+d[1][2]

image

因為5>3+1,所以將d[0][2]的值更新為4。

image

比較d[0][3]d[0][1]+d[1][3]

image

因為INF>3+4,所以將d[0][3]的值更新為7。

image

第0行接著的三個元素都不更新,到第2行後,比較d[2][0]d[2][1]+d[1][0]

image

因為5>1+3,所以將d[2][0]的值更新為4。第二行剩餘的元素都無需更新。

image

開始第3行,比較d[3][0]d[3][1]+d[1][0]

image

因為INF>4+3,所以將d[3][0]的值更新為7。

image

接著以頂點1作為中轉點時剩餘的全部元素都無需更新。

當以頂點2為中轉點時

逐一查詢看是否有以2為中轉點使得距離更短,比較d[0][1]d[0][2]+d[2][1],因為3<4+1,所以d[0][1]不更新。

image

比較d[0][3]d[0][2]+d[2][3],因為7<4+4,所以d[0][3]不更新。

image

比較d[0][4]d[0][2]+d[2][4]

image

因為INF>4+8,所以將d[0][4]的值更新為12。

image

類似地,對以頂點2作為中轉點的全部剩餘元素進行比較更新。

當以頂點3為中轉點時

逐一查詢看是否有以3為中轉點使得距離更短,比較d[0][1]d[0][3]+d[3][1],因為3<7+4,所以d[0][1]不更新。

image

比較d[0][2]d[0][3]+d[3][2],因為4<7+4,所以d[0][2]不更新。

image

類似地,對以頂點3作為中轉點的全部剩餘元素進行比較更新。

當以頂點4為中轉點時

逐一查詢看是否有以4為中轉點使得距離更短,比較d[0][1]d[0][4]+d[4][1],因為3<12+9,所以d[0][1]不更新。

image

比較d[0][2]d[0][4]+d[4][2],因為4<12+8,所以d[0][2]不更新。

image

類似地,對以頂點4作為中轉點的全部剩餘元素進行比較更新。

當以頂點5為中轉點時

逐一查詢看是否有以5為中轉點使得距離更短,比較d[0][1]d[0][5]+d[5][1],因為3<6+3,所以d[0][1]不更新。

image

比較d[0][2]d[0][5]+d[5][2],因為4<6+2,所以d[0][2]不更新。

image

類似地,對以頂點5作為中轉點的全部剩餘元素進行比較更新。

當以頂點6為中轉點時

逐一查詢看是否有以6為中轉點使得距離更短,比較d[0][1]d[0][6]+d[6][1],因為3<9+6,所以d[0][1]不更新。

image

比較d[0][2]d[0][6]+d[56][2],因為4<9+5,所以d[0][2]不更新。

image

類似地,對以頂點6作為中轉點的全部剩餘元素進行比較更新。

最終矩陣

將所有頂點作為中轉點處理後,最終得到了一個矩陣,這個矩陣就是圖的每個頂點兩兩最短距離矩陣。比如a[0][4]=12就是頂點0到頂點4的最短距離為12。同時可以看到距離矩陣是以對角線為軸對稱的。

image

-------------推薦閱讀------------

我的開源專案彙總(機器&深度學習、NLP、網路IO、AIML、mysql協議、chatbot)

為什麼寫《Tomcat核心設計剖析》

我的2017文章彙總——機器學習篇

我的2017文章彙總——Java及中介軟體

我的2017文章彙總——深度學習篇

我的2017文章彙總——JDK原始碼篇

我的2017文章彙總——自然語言處理篇

我的2017文章彙總——Java併發篇


跟我交流,向我提問:

歡迎關注: