前言:在求任意兩點間的最短路問題中,圖一般較爲稠密,使用Floyd算法可以在O(N ^ 3)的時間實現。當然也可以把每個點作爲起點,求解N次單源最短路徑問題,但較爲複雜。這裏介紹Floyd算法以及使用Floyd算法打印路徑和解決選址問題
算法分析
假設用d[k, i, j]表示“經過若干編號不超過k的結點”從i 到 j的最短路長度,這個問題可以劃分爲兩個子問題,經過編號不超過 k - 1 的結點從i到j或者從i 先到k ,再到j,所以有:
d[k, i, j] = min(d[k - 1, i, j], d[k - 1, i, k] + d[k - 1, k, j])
因爲一般解決稠密圖,所以使用鄰接矩陣。
Floyd算法的本質是動態規劃,k表示我們所劃分的階段,所以在循環的最外層,但我們寫的時候已經將其優化,像揹包問題等動態規劃中那樣,使用“滾動數組”優化。
所以纔有: d[i][j] = min(d[i][j] , d[i][k] + d[k][j]);
Floyd裸模板
給定一個n個點m條邊的有向圖,圖中可能存在重邊和自環,邊權可能爲負數。
再給定k個詢問,每個詢問包含兩個整數x和y,表示查詢從點x到點y的最短距離,如果路徑不存在,則輸出“impossible”。
數據保證圖中不存在負權迴路。
輸入格式
第一行包含三個整數n,m,k
接下來m行,每行包含三個整數x,y,z,表示存在一條從點x到點y的有向邊,邊長爲z。
接下來k行,每行包含兩個整數x,y,表示詢問點x到點y的最短距離。
輸出格式
共k行,每行輸出一個整數,表示詢問的結果,若詢問兩點間不存在路徑,則輸出“impossible”。
數據範圍
1≤n≤200,
1 ≤ k ≤ n2
1 ≤ m ≤20000
圖中涉及邊長絕對值均不超過10000。
輸入樣例:
3 3 2
1 2 1
2 3 2
1 3 1
2 1
1 3
輸出樣例:
impossible
1
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 210, INF = 1e9;
int n, m, q;
int d[N][N];
void flody()
{
for(int k = 1; k <= n; k ++ )
for(int i = 1; i <= n; i ++ )
for(int j = 1; j <= n; j ++ )
d[i][j] = min(d[i][j] , d[i][k] + d[k][j]);
}
int main()
{
cin >> n >> m >> q;
for(int i = 1; i <= n; i ++ )
for(int j = 1; j <= n; j ++ )
if(i == j) d[i][j] = 0;
else d[i][j] = INF;
while(m -- )
{
int a, b, c;
cin >> a >> b >> c;
d[a][b] = min(d[a][b], c);
}
flody();
while(q -- )
{
int x, y;
cin >> x >> y;
int t = d[x][y];
if(t > INF / 2) puts("impossible");
else cout << t << endl;
}
return 0;
}
Floyd打印最短路徑
path[i][j]數組存儲 i ~ j這條路上 j 前面那個點,假設爲k,i ~ j 拆開就是i~ k 和 k ~ j,可以求出k前面的點,遞歸將i ~ j上經過的所有點全部打印出來。
樣例:
4 5
1 2 1
1 3 4
2 3 1
3 4 1
1 4 6
1 4
輸出:
1 -> 4的最短路徑爲:1 2 3 4
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 210, INF = 1e6;
int n, m;
int d[N][N],path[N][N];
void init();
void floyd();
void printpath(int a, int b);
int main()
{
memset(d, 0x3f, sizeof d);
cout << "請輸入點、邊的數量:" << endl;
cin >> n >> m;
init();
cout << "請輸入邊的信息:" << endl;
for(int i = 0; i < m; i ++ )
{
int a, b, c;
cin >> a >> b >> c;
d[a][b] = min(d[a][b], c);
}
floyd();
cout << "請輸入查詢路徑:" << endl;
int x, y;
cin >> x >> y;
printf("%d -> %d的最短路徑爲:", x, y);
printpath(x, y);
cout << endl;
return 0;
}
void init()
{
for(int i = 1; i <= n; i ++ )
for(int j = 1; j <= n; j ++ )
{
if(i == j) d[i][j] = 0;
else d[i][j] = INF;
path[i][j] = i;
}
}
void floyd()
{
for(int k = 1; k <= n; k ++ )
for(int i = 1; i <= n; i ++ )
for(int j = 1; j <= n ; j++ )
{
if(d[i][j] > d[i][k] + d[k][j])
{
d[i][j] = d[i][k] + d[k][j]; // 從i ~ j 的最短路長度
path[i][j] = k;
}
}
}
void printpath(int a, int b)
{
if(a == b)
{
cout << a << " ";
return ;
}
int k = path[a][b];
printpath(a, k);
cout << b << " ";
}
選址問題
比如建造一個加油站,使所有村莊到加油站的距離最近,問加油站應該建在哪裏。
通過Floyd計算出每個點到所有點的最短距離總和,找到總和最小點的就是加油站的地址。
樣例可以自己模擬一個:
第一行輸入點數n和邊數m
然後m行依次輸入每個點的信息
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 210, INF = 1e6;
int n, m;
int d[N][N];
void floyd();
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; i ++ )
for(int j = 1; j <= n; j ++ )
if(i == j) d[i][j] = 0;
else d[i][j] = INF;
while(m -- )
{
int a, b, c;
cin >> a >> b >> c;
d[a][b] = min(d[a][b], c);
}
floyd();
int ans = INF; //存路經總和
int location = 0; //加油站位置
//確定加油站的位置
for(int i = 1; i <= n; i ++ )
{
int sum = 0; //用來更新每個點到其他所有點的距離之和
for(int j = 1; j <= n; j ++ )
sum += d[i][j]; //將此點到所有村莊的路程加起來
//求出路徑之和加起來最短的
if(sum < ans)
{
ans = sum;
location = i; //i即爲醫院所在位置
}
}
cout << "位置及最短路程總和爲:";
cout << location << " " << ans;
return 0;
}
void floyd()
{
for(int k = 1; k <= n; k ++ )
for(int i = 1; i <= n; i ++ )
for(int j = 1; j <= n; j ++ )
d[i][j] = min(d[i][j] , d[i][k] + d[k][j]);
}