圖解弗洛伊德演算法(每一對頂點之間的最短路徑問題)
技術標籤:演算法與資料結構弗洛伊德演算法floyd演算法java最短路徑問題
文章目錄
一、弗洛伊德演算法概述
在上一篇部落格 圖解迪傑斯特拉演算法(最短路徑問題) 中介紹了迪傑斯特拉演算法,該演算法用於求解單源最短路徑問題。所謂單源最短路徑路徑就是從某一個頂點出發,求解其到各個頂點的最短路徑。
那麼如果想要求解每一對頂點之間的最短路徑,該怎麼做呢?
其實可以對迪傑斯特拉進行簡單的改造,就可以實現上述目的。既然迪傑斯特拉是用於計算單個源點到各個頂點之間的最短路徑,那麼只需要對每個頂點都執行一次迪傑斯特拉演算法,便可以得到每一對頂點之間的最短路徑。迪傑斯特拉的時間複雜度是 O(n²),如果對每個頂點都執行一次,那麼時間複雜度將變為 O(n³)。
不過,對迪傑斯特拉演算法改造來計算每對頂點之間的最短路徑在程式碼實現上是比較複雜的,這裡我們學習一種新的演算法,叫做弗洛伊德(Floyd)演算法。
弗洛伊德演算法在求解每一對頂點之間的最短路徑問題的程式碼實現上相對於迪傑斯特拉演算法簡單很多,它的時間複雜度也是 O(n³)。
二、弗洛伊德演算法的思想與步驟
2.1 基本思想
通常從源點到終點,並不是直接到達的,而是需要經過許多中轉站來一步步到達。例如下圖,<V4,V5>、<V5,V2> 之間的距離分別為 4 和 5。可以看到,V4 和 V2 之間不能直接通達,如果要計算 V4 和 V2 之間的距離,我們就要藉助 V5 來作為中轉站,因此 V4 到 V2 的距離為 4+5=9。
如果要計算 V4 和 V3 之間的距離呢?這個時候只借助 V5 就無法完成任務了,還需要額外借助 V2。
因此,計算所有節點之間的最短距離這個大問題可以分為藉助 k 箇中轉站的情況下計算各節點之間的最短路徑,其中 0 ≤ k ≤ |V|。
弗洛伊德演算法的基本思想如下:
假設求從頂點 vi 到 vj 的最短路徑。如果從 vi 到 vj 有邊,則從 vi 到 vj 存在一條長度為 dis[i][j]
(dis 為鄰接矩陣)的路徑,但是該路徑不一定是最短路徑,還需要再進行 n 次試探。首先考慮路徑 <vi,v0,vj> 是否存在(即 <vi,v0> 、<v0,vj>),如果存在,則比較 <vi,vj> 和 <vi,v0,vj> 的路徑長度,然後取較小者為 vi 到 vj 的最短路徑,且 vi 到 vj 的中間頂點的序號不大於 0。假如在路徑上再增加一個一個頂點 v1,也就是說,如果 <vi,…,v1> 和 <v1,…,vj> 分別是當前找到的中間頂點的序號不大於 0 (即中間頂點可能是 v0 或沒有)的最短路徑,那麼<vi,…,v1,…,vj> 就有可能是從 vi 到 vj 的中間頂點的序號不大於 1 的最短路徑。將它和已經得到的從 vi 到 vj 中間頂點序號不大於 0 的最短路徑相比較,從中選出中間頂點的序號不大於 1 的最短路徑之後,再增加一個頂點 v2,繼續進行試探。以此類推,直到增加了所有的頂點作為中間節點。
弗洛伊德演算法的核心思想總結下來就是:不斷增加中轉頂點,然後更新每對頂點之間的最短距離。
2.2 步驟圖解說明
如下圖所示,演示使用弗洛伊德演算法求解每一對頂點之間的最短路徑。
-
在沒有中轉點的情況下,各個頂點之間的最短路徑距離,用一個二維陣列 dis 表示,如下圖所示。
-
假設 v1 作為中轉點,則各個頂點之間的最短路徑距離理論上將會被更新。然而由於 v2 經中轉點 v1 到 v3 的距離大於 v2 直接到 v3 的距離(即
dis[v2][v1] + dis[v1][v3] > dis[v2][v3]
),因此本次最短路徑距離並未真正發生變化。 -
再假設 v1,v2 作為中轉點。此時 v5 可以經 v2 訪問到 v1 和 v3,在沒有中轉的情況下,v5 到 v1 和 v3 的最短距離都是無窮大,因此各個頂點之間的最短路徑距離將會被更新。
-
再假設 v1,v2,v3 作為中轉點。此時 v1 經 v3 訪問 v2 的距離小於直接訪問 v2 的距離(即
dis[v1][v3] + dis[v3][v2] < dis[v1][v2]
),因此 v1 到 v2 的最短距離將會被更新;同時由於 v1 需經 v2 中轉訪問 v5,因此 v1 到 v5 的最短距離也要同步更新。 -
再假設 v1,v2,v3,v4 作為中轉點。由於 v4 是一個孤立的中轉點因此,因此各個頂點之間的最短路徑並未發生真正變化。
-
再假設 v1,v2,v3,v4,v5 作為中轉點。由於 v2 經 v5 中轉可達 v4 且距離小於
dis[v2][v4]
,因此 v2 到 v4 的最短路徑將會更新 ,且 v1,v3 到 v4 的最短路徑也會隨之更新。 -
至此,所有的點都作為了中轉點,演算法執行完畢。此時 dis 陣列中存放的就是各個頂點之間的最短路徑距離。
三、弗洛伊德演算法的程式碼實現
public class FloydAlgorithm {
private static final int N = 65535; // 表示距離無窮大
public static void main(String[] args) {
String[] vertexes = { "v1", "v2", "v3", "v4", "v5"}; // 頂點
int[][] dis = { // 各個頂點之間的初始最短路徑
/*v1*//*v2*//*v3*//*v4*//*v5*/
/*v1*/{0, 6, 3, N, N},
/*v2*/{6, 0, 2, N, 5},
/*v3*/{3, 2, 0, N, N},
/*v4*/{N, N, N, 0, 4},
/*v5*/{N, 5, N, 4, 0},
};
floyd(dis); // 呼叫弗洛伊德演算法
/* 輸出每一對頂點之間的最短距離 */
for (int i=0; i<dis.length; i++){
for (int j=0; j<dis[i].length; j++){
System.out.printf("<%s,%s> = %d \t",vertexes[i],vertexes[j],dis[i][j]);
}
System.out.println();
}
}
/**
* 弗洛伊德演算法求解每一對頂點之間的最短路徑距離
* @param dis 初始最短路徑距離
*/
public static void floyd(int[][] dis){
for (int k=0; k<dis.length; k++){ // 遍歷中轉頂點
for (int i=0; i<dis.length; i++){ // 從下標為 i 的頂點出發
for (int j=0; j<dis.length; j++){ // 終點下標為 j
/* 如果經新的中轉點後的最短路徑距離小於 dis[i][j],就更新 dis[i][j] */
if (dis[i][k] + dis[k][j] < dis[i][j]){
dis[i][j] = dis[i][k] + dis[k][j];
}
}
}
}
}
}
執行結果如下: