通過dij,ford,spfa等算法可以快速的得到單源點的最短路徑,如果想要得到圖中任意兩點之間的最短路徑,當然可以選擇做n遍的dij或是ford,但還有一個思維量較小的選擇,就是floyd算法。
多源最短路徑算法
Floyd算法
思維
先直觀做個思考,一張圖,任意兩個點,已知兩點間的路徑權值,如果在圖中能夠找到一個點插入到這兩點的路徑之中,使得構成的路徑權值小於之前的路徑權值。就可以認為這條路比之前的路更短,這個點是屬於兩點間最短路徑的。由此可以得到一個遞推公式:
\[e[u][v]=min(e[u][v],e[u][k]+e[k][v]) \]
問題就轉換成了e[u][k],e[k][v]最短路徑的問題,這就是一種動態規划。
只要把圖枚舉一遍就可以得到所有點之間的最短路徑:
for (int k = 1; k <= n; k++)
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
e[i][j] = min(e[i][j], e[i][k] + e[k][j]);
說到動態規范,總會感嘆遞推式的優美,但是有沒有想過,為什么k循環在最外層,而且由於e[i][k],e[k][j]也是在不斷更新,而不是恆定的最小值,所以如何保證算法在最后一次松弛更新的時候,e[i][k],e[k][j]一定是最小的。
我們用歸納法(一生之敵)做一次證明:
- 不妨做個命題:假設任意兩點i和j之間的路徑上可選擇經過的結點集合中,座號最大的是x,當k=x的時候,d[i][j]得到最小值。
- 當i,j兩點之間路徑可選擇經過結點僅有一個點時候,命題是成立的。
- 設i-x中座號最大的為x1,x-j中座號最大的為x2。
- 顯然x>x1,x>x2。
- 假設此時命題成立,則k=x1時,d[i][x]最小,k=x2時,d[x][j]最小。
- 由此可以得到k=x的時候d[i][x]+d[x][j]已經是最小了,那么e[i][j]=min(e[i][j],e[i][x]+e[x][j])必然可以得到最小值。
由此可以證明在k循環完后就能夠得到最小的。當然也可以畫張圖直觀的感受一下。
代碼實現
#include<iostream>
#include<algorithm>
using namespace std;
const int MAX = 1000;
int e[MAX][MAX];
int n, m;
void Floyd() {
for (int k = 1; k <= n; k++)
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
e[i][j] = min(e[i][j], e[i][k] + e[k][j]);
}
int main() {
cin >> n >> m;
const int inf = 100000;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
if (i == j) e[i][j] = 0;
else e[i][j] = inf;
int u, v, w;
for (int i = 0; i < m; i++) {
cin >> u >> v >> w;
e[u][v] = w;
e[v][u] = w;
}
Floyd();
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
cout << e[i][j] << " ";
return 0;
}