1. 程式人生 > 實用技巧 >簡介Floyd演算法

簡介Floyd演算法

目錄

1. 定義

Floyd演算法是一種用於尋找給定的加權圖中頂點間最短路徑,是經典的多源最短路徑演算法,可以有效地處理有向圖或負權的最短路徑問題,同時也被用於計算有向圖的傳遞閉包。

Floyd演算法的時間複雜度為 \(O(N^3)\),空間複雜度為 \(O(N^2)\)

2. 基本思想

Floyd演算法屬於動態規劃演算法,即尋找節點 \(i\) 到節點 \(j\) 的最短路徑。

Step 1: 初始距離
定義 \(n\) 節點網路的鄰接矩陣 \(A_{n\times n}\) ,矩陣中的元素為 \(a_{i,j}\) 為節點 \(i\)

到節點 \(j\)一步的直線距離。令 \(A^{(0)}=A\),其初始元素為 \(a_{i,j}^{(0)}\)
則該距離有如下三種情況:

\[a_{i,j}^{(0)}= \begin{cases} c_{i,j},\ i,j相連 \\ 0,\ i=j \\ \infty,\ i,j不相連 \end{cases} \]

其中,節點 \(i,\ j\) 之間有直線連線時,則 \(a_{i,j}^{(0)}\) 為其距離值 \(c_{i,j}\);節點 \(i\) 到自身的距離為 \(0\);節點 \(i,\ j\) 之間沒有直線連線時,則 \(a_{i,j}^{(0)}\) 則為 \(\infty\)

,如下圖:

則該初始鄰接矩陣 \(A\)為:

即節點0與節點0自身距離值為0,即 \(A[0][0]=0\)
節點0與節點1之間有直線連線,距離值為5,即 \(A[0][1]=5\)
節點0與節點2之間沒有直線連線,則距離值為 \(\infty\),即 \(A[0][2]=\infty\)
節點0與節點3之間有直線連線,距離值為7,即 \(A[0][3]=7\) ……
其他節點間的初始距離可依次寫出,即為該鄰接矩陣 \(A\)

Step 2: 借中轉節點迭代找最短路徑
節點 \(i,\ j\) 間一步達不到時,則需要在兩節點之間通過其他節點(如節點 \(k\))作連線:
\(A\) 矩陣上做 \(n\)

次迭代,\(k=1,\cdots,n\),第 \(k\) 次迭代

\[a_{i,j}^{k}=min(a_{i,j}^{k-1},a_{i,k}^{k-1}+a_{k,j}^{k-1}) \]

即在節點 \(i\) 和節點 \(j\) 之間找到一條最短距離的路徑,如下圖:

圖中的節點 \(i\) 到節點 \(j\)之間的直線距離 \((i\rightarrow j)\)\(6\),但經過節點 \(k\) 作中轉後,節點 \(i\) 到節點 \(j\)之間的直線距離 \((i\rightarrow k\rightarrow j)\)\(2+3=5\),因此 \(a_{i,j}^{k}=min(6,5)=5\)
運算過程中的 \(k\)\(1\) 開始,而節點 \(i,\ j\) 則分別從 \(1\)\(n\) 遍歷所有的值,然後 \(k\) 加1,直到 \(k\) 等於 \(n\) 時停止。

Step 3: 得到所有節點的最短路徑
遍歷所有的節點,最後得到矩陣 \(A\),其中 \(a_{i,j}\) 便儲存了任意節點 \(i\) 到節點 \(j\) 頂點的最短路徑的距離。

3. 演算法過程示例

基於上述的基本思想,定義兩個二維矩陣:

鄰接矩陣 A 記錄節點間的最短距離
      例如:A[0][3]=10,即表示節點 0 與節點 3 之間最短距離為10 

路徑矩陣 P 記錄節點間最短路徑中的中轉節點 
      例如:P[0][3]=1,即表示節點 0 與節點 3 之間的最短路徑軌跡為:(0→1→3)

採用上面的節點圖
Step 1: 初始的鄰接矩陣 \(A\) 和路徑矩陣 \(P\) 分別為:

路徑矩陣 \(P\) 還未開始查詢,預設為 \(-1\)


Step 2: 開始迭代
T1: 節點 \(0\) 做中轉節點:

過程:
節點 \(1\) 到節點 \(2\):{1,2}:比較 \(A[1][2]>(A[1][0]+A[0][2])\ ? \rightarrow 4>(\infty +\infty) \ ? \rightarrow A[1][2]=4\) 不變
節點 \(1\) 到節點 \(3\):{1,3}:比較 \(A[1][3]>(A[1][0]+A[0][3])\ ? \rightarrow 2>(\infty +7) \ ? \rightarrow A[1][2]=2\) 不變
節點 \(2\) 到節點 \(1\):{2,1}:比較 \(A[2][1]>(A[2][0]+A[0][1])\ ? \rightarrow 3>(3 +5) \ ? \rightarrow A[1][2]=3\) 不變
節點 \(2\) 到節點 \(3\):{2,3}:比較 \(A[2][3]>(A[2][0]+A[0][3])\ ? \rightarrow 2>(3 +7) \ ? \rightarrow A[1][2]=2\) 不變
節點 \(3\) 到節點 \(1\):{3,1}:比較 \(A[3][1]>(A[3][0]+A[0][1])\ ? \rightarrow \infty>(\infty +5) \ ? \rightarrow A[3][1]=\infty\) 不變
節點 \(3\) 到節點 \(2\):{3,2}:比較 \(A[3][2]>(A[3][0]+A[0][2])\ ? \rightarrow 1>(\infty +\infty) \ ? \rightarrow A[3][2]=1\) 不變


T2: 節點 \(1\) 做中轉節點:

過程:
節點 \(0\) 到節點 \(2\):{0,2}:比較 \(A[0][2]>(A[0][1]+A[1][2])\ ? \rightarrow 9>(5 +4) \ ? \rightarrow A[0][2]=9\) 更改:經過節點 \(1\)
節點 \(0\) 到節點 \(3\):{0,3}:比較 \(A[0][3]>(A[0][1]+A[1][3])\ ? \rightarrow 7>(5 +2) \ ? \rightarrow A[0][3]=7\) 不變
節點 \(2\) 到節點 \(0\):{2,0}:比較 \(A[2][0]>(A[2][1]+A[1][0])\ ? \rightarrow 3>(3 +\infty) \ ? \rightarrow A[2][0]=3\) 不變
節點 \(2\) 到節點 \(3\):{2,3}:比較 \(A[2][3]>(A[2][1]+A[1][3])\ ? \rightarrow 2>(3 +2) \ ? \rightarrow A[2][3]=2\) 不變
節點 \(3\) 到節點 \(0\):{3,0}:比較 \(A[3][0]>(A[3][1]+A[1][0])\ ? \rightarrow \infty>(\infty +\infty) \ ? \rightarrow A[3][0]=\infty\) 不變
節點 \(3\) 到節點 \(2\):{3,2}:比較 \(A[3][2]>(A[3][1]+A[1][2])\ ? \rightarrow 1>(\infty +4) \ ? \rightarrow A[3][2]=1\) 不變


T3: 節點 \(2\) 做中轉節點:

過程:
節點 \(0\) 到節點 \(1\):{0,1}:比較 \(A[0][1]>(A[0][2]+A[2][1])\ ? \rightarrow 5>(9 +3) \ ? \rightarrow A[0][1]=5\) 不變
節點 \(0\) 到節點 \(3\):{0,3}:比較 \(A[0][3]>(A[0][2]+A[2][3])\ ? \rightarrow 7>(9 +2) \ ? \rightarrow A[0][3]=7\) 不變
節點 \(1\) 到節點 \(0\):{1,0}:比較 \(A[1][0]>(A[1][2]+A[2][0])\ ? \rightarrow \infty>(4 +3) \ ? \rightarrow A[1][0]=7\) 改變:經過節點 \(2\)
節點 \(1\) 到節點 \(3\):{1,3}:比較 \(A[1][3]>(A[1][2]+A[2][3])\ ? \rightarrow 2>(4 +2) \ ? \rightarrow A[1][3]=2\) 不變
節點 \(3\) 到節點 \(0\):{3,0}:比較 \(A[3][0]>(A[3][2]+A[2][0])\ ? \rightarrow \infty>(1 +3) \ ? \rightarrow A[3][0]=4\) 改變:經過節點 \(2\)
節點 \(3\) 到節點 \(1\):{3,1}:比較 \(A[3][1]>(A[3][2]+A[2][1])\ ? \rightarrow \infty>(1+3) \ ? \rightarrow A[3][1]=4\) 改變:經過節點 \(2\)


T4: 節點 \(3\) 做中轉節點:

過程:
節點 \(0\) 到節點 \(1\):{0,1}:比較 \(A[0][1]>(A[0][3]+A[3][1])\ ? \rightarrow 5>(9 +4) \ ? \rightarrow A[0][1]=5\) 不變
節點 \(0\) 到節點 \(2\):{0,2}:比較 \(A[0][2]>(A[0][3]+A[3][2])\ ? \rightarrow 9>(7 +1) \ ? \rightarrow A[0][2]=8\) 改變:經過節點 \(3\)
節點 \(1\) 到節點 \(0\):{1,0}:比較 \(A[1][0]>(A[1][3]+A[3][0])\ ? \rightarrow 7>(2 +4) \ ? \rightarrow A[1][0]=6\) 改變:經過節點 \(3\)
節點 \(1\) 到節點 \(2\):{1,2}:比較 \(A[1][2]>(A[1][3]+A[3][2])\ ? \rightarrow 4>(2 +1) \ ? \rightarrow A[1][2]=3\) 改變:經過節點 \(3\)
節點 \(2\) 到節點 \(0\):{2,0}:比較 \(A[2][0]>(A[2][3]+A[3][0])\ ? \rightarrow 3>(2 +4) \ ? \rightarrow A[2][0]=3\) 不變
節點 \(2\) 到節點 \(1\):{2,1}:比較 \(A[2][1]>(A[2][3]+A[3][1])\ ? \rightarrow 3>(2+4) \ ? \rightarrow A[2][1]=3\) 不變

Step 3: 得到所有節點的最短路徑
最終的鄰接矩陣 \(A\) 和路徑矩陣 \(P\)為:

從上圖可知:
從鄰接矩陣 \(A_4\) 可知節點 \(1\) 到節點 \(0\) (\(1 \rightarrow 0\))的最短路徑距離為 \(6\)
從路徑矩陣 \(P_4\) 可知節點 \(1\) 到節點 \(0\) (\(1 \rightarrow 0\))的最短路徑為 \(1 \rightarrow 3 \rightarrow 2 \rightarrow 0\)

4. 小結

Floyd演算法採用中轉節點的方式,逐步對比得到各個路徑的最短距離。