Floyd-Warshall演算法詳解(轉)
Floyd-Warshall演算法,簡稱Floyd演算法,用於求解任意兩點間的最短距離,時間複雜度為O(n^3)。我們平時所見的Floyd演算法的一般形式如下:
1 void Floyd(){
2 int i,j,k;
3 for(k=1;k<=n;k++)
4 for(i=1;i<=n;i++)
5 for(j=1;j<=n;j++)
6 if(dist[i][k]+dist[k][j]<dist[i][j])
7 dist[i][j]=dist[i][k]+dist[k][j];
8 }
注意下第6行這個地方,如果dist[i][k]或者dist[k][j]不存在,程式中用一個很大的數代替。最好寫成if(dist[i][k]!=INF && dist[k][j]!=INF && dist[i][k]+dist[k][j] < dist[i][j]),從而防止溢位所造成的錯誤。
上面這個形式的演算法其實是Floyd演算法的精簡版,而真正的Floyd演算法是一種基於DP(Dynamic Programming)的最短路徑演算法。
設圖G中n 個頂點的編號為1到n。令c [i, j, k]表示從i 到j 的最短路徑的長度,其中k 表示該路徑中的最大頂點,也就是說c[i,j,k]這條最短路徑所通過的中間頂點最大不超過k。因此,如果G中包含邊 < i, j>,則c[i, j, 0] =邊 < i, j> 的長度;若i= j ,則c[i,j,0]=0;如果G中不包含邊 < i, j>,則c (i, j, 0)= +∞。c[i, j, n] 則是從i 到j 的最短路徑的長度。
對於任意的k>0,通過分析可以得到:中間頂點不超過k 的i 到j 的最短路徑有兩種可能:該路徑含或不含中間頂點k。若不含,則該路徑長度應為c[i, j, k-1],否則長度為 c[i, k, k-1] +c [k, j, k-1]。c[i, j, k]可取兩者中的最小值。
狀態轉移方程:c[i, j, k]=min{c[i, j, k-1], c [i, k, k-1]+c [k, j, k-1]},k>0。
這樣,問題便具有了最優子結構性質,可以用動態規劃方法來求解。
為了進一步理解,觀察上面這個有向圖:若k=0, 1, 2, 3,則c[1,3,k]= +∞;c[1,3,4]= 28;若k = 5, 6, 7,則c [1,3,k] = 10;若k=8, 9, 10,則c[1,3,k] = 9。因此1到3的最短路徑長度為9。
下面通過程式來分析這一DP過程,對應上面給出的有向圖:
1 #include <iostream>
2 using namespace std;
3
4 const int INF = 100000;
5 int n=10,map[11][11],dist[11][11][11];
6 void init(){
7 int i,j;
8 for(i=1;i<=n;i++)
9 for(j=1;j<=n;j++)
10 map[i][j]=(i==j)?0:INF;
11 map[1][2]=2,map[1][4]=20,map[2][5]=1;
12 map[3][1]=3,map[4][3]=8,map[4][6]=6;
13 map[4][7]=4,map[5][3]=7,map[5][8]=3;
14 map[6][3]=1,map[7][8]=1,map[8][6]=2;
15 map[8][10]=2,map[9][7]=2,map[10][9]=1;
16 }
17 void floyd_dp(){
18 int i,j,k;
19 for(i=1;i<=n;i++)
20 for(j=1;j<=n;j++)
21 dist[i][j][0]=map[i][j];
22 for(k=1;k<=n;k++)
23 for(i=1;i<=n;i++)
24 for(j=1;j<=n;j++){
25 dist[i][j][k]=dist[i][j][k-1];
26 if(dist[i][k][k-1]+dist[k][j][k-1]<dist[i][j][k])
27 dist[i][j][k]=dist[i][k][k-1]+dist[k][j][k-1];
28 }
29 }
30 int main(){
31 int k,u,v;
32 init();
33 floyd_dp();
34 while(cin>>u>>v,u||v){
35 for(k=0;k<=n;k++){
36 if(dist[u][v][k]==INF) cout<<"+∞"<<endl;
37 else cout<<dist[u][v][k]<<endl;
38 }
39 }
40 return 0;
41 }
輸入 1 3
輸出 +∞
+∞
+∞
+∞
28
10
10
10
9
9
9
Floyd-Warshall演算法不僅能求出任意2點間的最短路徑,還可以儲存最短路徑上經過的節點。下面用精簡版的Floyd演算法實現這一過程,程式中的圖依然對應上面的有向圖。
1 #include <iostream>
2 using namespace std;
3
4 const int INF = 100000;
5 int n=10,path[11][11],dist[11][11],map[11][11];
6 void init(){
7 int i,j;
8 for(i=1;i<=n;i++)
9 for(j=1;j<=n;j++)
10 map[i][j]=(i==j)?0:INF;
11 map[1][2]=2,map[1][4]=20,map[2][5]=1;
12 map[3][1]=3,map[4][3]=8,map[4][6]=6;
13 map[4][7]=4,map[5][3]=7,map[5][8]=3;
14 map[6][3]=1,map[7][8]=1,map[8][6]=2;
15 map[8][10]=2,map[9][7]=2,map[10][9]=1;
16 }
17 void floyd(){
18 int i,j,k;
19 for(i=1;i<=n;i++)
20 for(j=1;j<=n;j++)
21 dist[i][j]=map[i][j],path[i][j]=0;
22 for(k=1;k<=n;k++)
23 for(i=1;i<=n;i++)
24 for(j=1;j<=n;j++)
25 if(dist[i][k]+dist[k][j]<dist[i][j])
26 dist[i][j]=dist[i][k]+dist[k][j],path[i][j]=k;
27 }
28 void output(int i,int j){
29 if(i==j) return;
30 if(path[i][j]==0) cout<<j<<' ';
31 else{
32 output(i,path[i][j]);
33 output(path[i][j],j);
34 }
35 }
36 int main(){
37 int u,v;
38 init();
39 floyd();
40 while(cin>>u>>v,u||v){
41 if(dist[u][v]==INF) cout<<"No path"<<endl;
42 else{
43 cout<<u<<' ';
44 output(u,v);
45 cout<<endl;
46 }
47 }
48 return 0;
49 }
輸入 1 3
輸出 1 2 5 8 6 3
floyd演算法
弗洛伊德(Floyd)演算法過程:
1、用D[v][w]記錄每一對頂點的最短距離。
2、依次掃描每一個點,並以其為基點再遍歷所有每一對頂點D[][]的值,看看是否可用過該基點讓這對頂點間的距離更小。
演算法理解:
最短距離有三種情況:
1、兩點的直達距離最短。(如下圖 < v,x>)
2、兩點間只通過一箇中間點而距離最短。(圖 < v,u>)
3、兩點間用通過兩各以上的頂點而距離最短。(圖 < v,w>)
對於第一種情況:在初始化的時候就已經找出來了且以後也不會更改到。
對於第二種情況:弗洛伊德演算法的基本操作就是對於每一對頂點,遍歷所有其它頂點,看看可否通過這一個頂點讓這對頂點距離更短,也就是遍歷了圖中所有的三角形(演算法中對同一個三角形掃描了九次,原則上只用掃描三次即可,但要加入判斷,效率更低)。
對於第三種情況:如下圖的五邊形,可先找一點(比如x,使< v,u>=2),就變成了四邊形問題,再找一點(比如y,使 < u,w>=2),可變成三角形問題了(v,u,w),也就變成第二種情況了,由此對於n邊形也可以一步步轉化成四邊形三角形問題。(這裡面不用擔心哪個點要先找哪個點要後找,因為找了任一個點都可以使其變成(n-1)邊形的問題)。
floyd的核心程式碼:
for (k=0;k<g.vexnum;k++)
{
for (i=0;i<g.vexnum;i++)
{
for (j=0;j<g.vexnum;j++)
{
if (distance[i][j]>distance[i][k]+distance[k][j])
{
distance[i][j]=distance[i][k]+distance[k][j];
}
}
}
}
結合程式碼 並參照上圖所示 我們來模擬執行下 這樣才能加深理解:
第一關鍵步驟:當k執行到x,i=v,j=u時,計算出v到u的最短路徑要通過x,此時v、u聯通了。
第二關鍵步驟:當k執行到u,i=v,j=y,此時計算出v到y的最短路徑的最短路徑為v到u,再到y(此時v到u的最短路徑上一步我們已經計算過來,直接利用上步結果)。
第三關鍵步驟:當k執行到y時,i=v,j=w,此時計算出最短路徑為v到y(此時v到y的最短路徑長在第二步我們已經計算出來了),再從y到w。
依次掃描每一點(k),並以該點作為中介點,計算出通過k點的其他任意兩點(i,j)的最短距離,這就是floyd演算法的精髓!同時也解釋了為什麼k點這個中介點要放在最外層迴圈的原因.
//多源最短路徑,floyd_warshall演算法,複雜度O(n^3)
//求出所有點對之間的最短路經,傳入圖的大小和鄰接陣
//返回各點間最短距離min[]和路徑pre[],pre[i][j]記錄i到j最短路徑上j的父結點
//可更改路權型別,路權必須非負!
#define MAXN 200
#define inf 1000000000
typedef int elem_t;
void floyd_warshall(int n,elem_t mat[][MAXN],elem_t min[][MAXN],int pre[][MAXN]){
int i,j,k;
for (i=0;i<n;i++)
for (j=0;j<n;j++)
min[i][j]=mat[i][j],pre[i][j]=(i==j)?-1:i;
for (k=0;k<n;k++)
for (i=0;i<n;i++)
for (j=0;j<n;j++)
if (min[i][k]+min[k][j]<min[i][j])
min[i][j]=min[i][k]+min[k][j],pre[i][j]=pre[k][j];
}