Floyd算法是經典的求算多源最短路徑的算法,它的實質還是一種動態規划思想的應用。
一、Floyd算法的實現思想
Floyd算法是如何實現的呢,我下面做簡單說明:
我們要求算i,j兩點間的最短距離,首先我們引入一個中間點k,看看從i到j有沒有一條經過k的通路(即i→k→j),如果有這么一條路,那么我們將目前的從i到j的距離,與從i到k再到j的距離相比較,小的那一個更新為新的從i到j的最短路。
那么用dp寫出它的狀態轉移方程有:

那么在代碼里我們要怎樣來實現呢?
首先需要一個矩陣來儲存各個點的關系,對於下面一個圖,我們可以得到相應的關系矩陣(數字代表由i到j的路徑長度,INF代表目前無法連通):
首先需要一個矩陣來儲存各個點的關系,對於下面一個圖,我們可以得到相應的關系矩陣(數字代表由i到j的路徑長度,INF代表目前無法連通):


其中第i行第j列就代表從點i到點j的距離。
然后我們需要三層循環,最外層k遍歷中間點,里面為i,j遍歷矩陣,結合狀態轉移方程得到Floyd算法的核心代碼:
1 void floyd() 2 { 3 int i,j,k; 4 for(k=1;k<=n;k++) 5 for(i=1;i<=n;i++) 6 for(j=1;j<=n;j++) 7 { 8 if(dp[i][j]>dp[i][k]+dp[k][j]) 9 dp[i][j]=dp[i][k]+dp[k][j]; 10 } 11 }
顯然我們可以從代碼里看出,該算法的時間復雜度為O()。
首先令k=1,開始遍歷i,j,對於第(i,j)個關系來說,我們要把它本身的值和第i行第k個、第j列第k個的值的和相比較,取小的作為新的值。如下圖

對於第2行第5列的值來說,它的新的值是第2行第1個值和第5列第1個值的和。按照同樣的方法我們把k=1的遍歷走完

然后我們令k=2再開始遍歷

同理,對於第1行第3列的值來說,它的新的值是第1行第2個值和第3列第2個值的和。然后我們把k=2的情況遍歷完

然后我們接着把k=3,4,5…全部遍歷完,得到我們的最短路的矩陣

Floyd算法可以求解大多數情況,但是注意Floyd算法無法求解有負權回路的狀況,如

對於這個回路來說,它每進行一次循環,最短路就會減少1,所以永遠也找不到最短路。
二、Floyd算法的正確性
(施工中orz)
三、Floyd算法的其他應用
1.求解傳遞閉包
將Floyd算法稍作修改便可以得到求解傳遞閉包的代碼:
1 void floyd() 2 { 3 int i,j,k; 4 for(k=1;k<=n;k++) 5 for(i=1;i<=n;i++) 6 for(j=1;j<=n;j++) 7 if(matrix[i][k]&&matrix[k][j]) 8 matrix[i][j]=1; 9 }
若有會Warshall算法求傳遞閉包的朋友便會發現Floyd算法和Warshall算法高度相似!
相關題目:ZOJ P4124
代碼:

1 #include <bits/stdc++.h> 2 3 using namespace std; 4 5 int n,m,matrix[105][105],num[2][105],flag=0; 6 7 void floyd() 8 { 9 int i,j,k; 10 11 for(k=1;k<=n;k++) //floyd 12 for(i=1;i<=n;i++) 13 for(j=1;j<=n;j++) 14 if(matrix[i][k]&&matrix[k][j]) 15 matrix[i][j]=1; 16 17 for(i=1;i<=n;i++) //判斷是否有自環 18 for(j=1;j<=n;j++) 19 if(matrix[i][j]&&matrix[j][i]) 20 { 21 flag=1; 22 return; 23 } 24 25 for(i=1;i<=n;i++) //維護更新num數組 26 for(j=1;j<=n;j++) 27 if(matrix[i][j]) 28 { 29 num[0][i]++; 30 num[1][j]++; 31 } 32 } 33 34 int main() 35 { 36 int t; 37 scanf("%d",&t); 38 while(t--) 39 { 40 int i,a,b; 41 42 memset(matrix,0,sizeof(matrix)); //初始化 43 memset(num,0,sizeof(num)); 44 flag=0; 45 46 scanf("%d%d",&n,&m); 47 for(i=0;i<m;i++) //預處理 48 { 49 scanf("%d%d",&a,&b); 50 matrix[a][b]=1; 51 } 52 floyd(); 53 if(flag==0) //輸出,當一個數比它大的和比它小的都小於n/2時,可視為該項為中間項 54 { 55 for(i=1;i<=n;i++) 56 { 57 if(num[0][i]<=n/2&&num[1][i]<=n/2) printf("1"); 58 else printf("0"); 59 } 60 printf("\n"); 61 } 62 else 63 { 64 for(i=0;i<n;i++) printf("0"); 65 printf("\n"); 66 } 67 } 68 return 0; 69 }
(用Floyd算法解此題算是一個比較巧妙的解法)
四、相關題目
1.例題 Luogu P2910
代碼:

1 #include <bits/stdc++.h> 2 3 using namespace std; 4 5 int matrix[105][105],n; 6 7 void floyd() 8 { 9 int i,j,k; 10 for(k=1;k<=n;k++) 11 for(i=1;i<=n;i++) 12 for(j=1;j<=n;j++) 13 { 14 matrix[i][j]=min(matrix[i][j],matrix[i][k]+matrix[k][j]); 15 } 16 } 17 18 19 int main() 20 { 21 int i,j,m,order[10005]={0},ans=0; 22 scanf("%d%d",&n,&m); 23 for(i=0;i<m;i++) scanf("%d",&order[i]); 24 for(i=1;i<=n;i++) 25 for(j=1;j<=n;j++) scanf("%d",&matrix[i][j]); 26 floyd(); 27 for(i=0;i<m-1;i++) 28 ans+=matrix[order[i]][order[i+1]]; 29 printf("%d",ans); 30 return 0; 31 }
Author : Houge Date : 2019.6.1
Update log :