Floyd-Warshall Algorithm(弗洛伊德-沃捨爾算法)是解決圖中所有節點之間最短路徑的經典算法之一,它是一種動態規划算法,以及解決所有點對之間最短路徑的做法,同時也適用於帶有負邊權的圖。
算法的基本思想是維護一個二維數組D,用來記錄任意兩點之間的最短路徑長度。初始時,D[i][j]表示從節點i到節點j的距離,如果i和j不直接相鄰,那麼D[i][j]=正無窮。算法每進行一輪迭代,便會將D[i][j]和D[i][k]+D[k][j](相當於以k作為中介)進行比較更新,最終得到任意兩點之間的最短路徑長度。
以下是弗洛伊德-沃捨爾算法的偽代碼:
1. for k from 1 to |V|
2. for i from 1 to |V|
3. for j from 1 to |V|
4. if D[i][j] > D[i][k] + D[k][j] then
5. D[i][j] = D[i][k] + D[k][j]
舉例說明:
考慮下圖:
構建鄰接矩陣如下:
1 2 3 4 5 6
-------------------------
1 | 0 1 ∞ ∞ 5 ∞
2 | ∞ 0 1 ∞ ∞ ∞
3 | ∞ ∞ 0 2 ∞ ∞
4 | ∞ ∞ ∞ 0 ∞ 1
5 | ∞ ∞ ∞ 1 0 2
6 | ∞ ∞ ∞ ∞ ∞ 0
其中,∞表示兩點之間沒有連通路徑。
接下來,我們可以運用Floyd-Warshall Algorithm算法來求得任意兩點之間的最短路徑長度,具體過程如下:
第一輪迭代:k=1
D[1][2]=min(∞, ∞+1)=1,
D[1][5]=min(5, ∞+1)=6,
D[2][3]=min(∞,∞+1)=1,
D[3][4]=min(∞,∞+2)=2,
D[4][6]=min(∞,∞+1)=1,
D[5][6]=min(∞,∞+2)=2
1 2 3 4 5 6
-------------------------
1 | 0 1 ∞ ∞ 5 ∞
2 | ∞ 0 1 ∞ ∞ ∞
3 | ∞ 2 0 2 ∞ ∞
4 | ∞ ∞ ∞ 0 ∞ 1
5 | ∞ ∞ ∞ 1 0 2
6 | ∞ ∞ ∞ ∞ ∞ 0
第二輪迭代:k=2
D[1][3]=min(∞,1+1)=2,
D[1][6]=min(∞,5+2)=7,
D[2][4]=min(1,1+∞)=1,
D[3][6]=min(∞,2+2)=4,
D[4][5]=min(∞,1+2)=3
1 2 3 4 5 6
-------------------------
1 | 0 1 2 ∞ 5 7
2 | ∞ 0 1 2 ∞ 3
3 | ∞ 2 0 2 ∞ 4
4 | ∞ ∞ ∞ 0 3 1
5 | ∞ ∞ ∞ 1 0 2
6 | ∞ ∞ ∞ ∞ ∞ 0
第三輪迭代:k=3
D[1][4]=min(∞,2+2)=4,
D[2][5]=min(∞,1+2)=3,
D[3][6]=min(2,2+2)=2,
D[4][6]=min(1,3+1)=1,
D[5][3]=min(∞,2+2)=2
1 2 3 4 5 6
-------------------------
1 | 0 1 2 4 5 6
2 | ∞ 0 1 2 3 3
3 | ∞ 2 0 2 4 2
4 | ∞ ∞ ∞ 0 3 1
5 | ∞ ∞ 2 1 0 2
6 | ∞ ∞ ∞ ∞ ∞ 0
第四輪迭代:k=4
D[2][6]=min(3,3+1)=3,
D[3][5]=min(2,2+4)=2,
D[4][2]=min(2,3+1)=2,
D[4][6]=min(1,3+1)=1,
D[5][1]=min(∞,2+5)=7
1 2 3 4 5 6
-------------------------
1 | 0 1 2 4 5 6
2 | ∞ 0 1 2 3 3
3 | ∞ 2 0 2 4 2
4 | ∞ 2 3 0 3 1
5 | 7 6 2 1 0 2
6 | ∞ ∞ ∞ ∞ ∞ 0
最終的結果是:
1 2 3 4 5 6
-------------------------
1 | 0 1 2 4 5 6
2 | ∞ 0 1 2 3 3
3 | ∞ 2 0 2 4 2
4 | ∞ 2 3 0 3 1
5 | 7 6 2 1 0 2
6 | ∞ ∞ ∞ ∞ ∞ 0
因此,我們可以得到所有點對之間的最短距離。例如,點1到點6的最短距離為6,點2到點1的最短距離為∞,點4到點5的最短距離為3 等。
總結:Floyd-Warshall Algorithm算法對有些權值為負的圖仍然有效,並且由於操作矩陣,理論上算法時間複雜度為O(n^3),適用於處理規模不是特別大的圖,而且不受圖形中負邊權的影響,非常適合多源最短路徑問題。
程式碼如下:
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][k] != INF && d[k][j] != INF && d[i][j] > d[i][k] + d[k][j]) {
d[i][j] = d[i][k] + d[k][j];
}
}
}
}
}
程式碼如下:
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][k] != INF && d[k][j] != INF && d[i][j] > d[i][k] + d[k][j]) {
d[i][j] = d[i][k] + d[k][j];
}
}
}
}
}
程式碼如下:
int 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][k] != INF && d[k][j] != INF && d[i][j] > d[i][k] + d[k][j]) {
d[i][j] = d[i][k] + d[k][j];
}
}
}
}
for (int k = 1; k <= n; k++) {
if (d[k][k] < 0) { // 存在負權環
return -1;
}
}
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++) {
if (d[i][k] != INF && d[k][j] != INF && d[i][j] > d[i][k] + d[k][j]) {
d[i][j] = d[i][k] + d[k][j];
}
}
}
}
for (int i = 1; i <= n; i++) {
int sum = 0;
for (int j = 1; j <= n; j++) {
if (d[i][j] != INF) {
sum += d[i][j];
}
}
printf("%d ", sum);
}
}
5. 給定一張有向帶權圖,求從任意一點到任意一點經過正好k條邊的最短路徑。
程式碼如下:
void Floyd(int k) {
for (int l = 1; l <= k; l++) { // l表示經過l條邊
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
for (int m = 1; m <= n; m++) {
if (d[i][m] != INF && d[m][j] != INF && d[i][j] > d[i][m] + d[m][j]) {
d[i][j] = d[i][m] + d[m][j];
}
}
}
}
}
}