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];
}
}
}
}
}
}