【专题】Floyd-Warshall

简介

Floyd-Warshall算法是求解多源最短路的算法之一,核心思想是动态规划,适用于不包含负环(负权回路)的图。时间复杂度\(O(n^{3})\),空间复杂度\(O(n^{2})\)

Floyd-Warshall算法

对于任何一个点而言,i到j的最短距离不外乎i到j经过k不经过k两种可能,所以可以令\(k=1,2,3,\ldots,n\),检查\(dis(i, j)\)\(dis(i, k)+dis(k, j)\)的大小,在此\(dis(i, k)\)\(dis(k, j)\)分别是目前为止所知的i到k与k到j的最短距离,因此\(dis(i, k)+dis(k, j)\)就是i到j经过k的最短距离。

所以,若有\(dis(i, j) \gt dis(i, k)+dis(k, j)\),就表示从i出发经过k再到j的距离要比原来的i到j距离短,自然把i到j的距离\(dis(i, j)\)更新为\(dis(i, k)+dis(k, j)\),更新完后\(dis(i, j)\)就是目前的i到j的最短距离。重复这一过程,最后当遍历完k时,\(dis(i, j)\)里面存放的就是i到j之间的最短距离了。

我们定义数组\(dp[k][x][y]\)表示只允许经过结点1到k,结点x到结点y的最短路径长度,那么状态转移方程就是\(dp[k][x][y] = min(dp[k-1][x][y], dp[k-1][x][k]+dp[k-1][k][y])\),可以发现第一维是没有用的,于是直接改成\(dp[x][y] = min(dp[x][y], dp[x][k]+dp[k][y])\)

代码实现很简单,不过要注意循环的嵌套循序,k作为阶段,枚举时的循环要放在最外层:

1
2
3
4
5
6
7
void floyd(int n)
{
for(int k=1; k<=n; k++)
for(int i=1; i<=n; i++)
for(int j=1; j<=n; j++)
g[i][j] = min(g[i][j], g[i][k]+g[k][j]);
}

*传递闭包

Floyd算法不仅可以求最短路,也可以维护关系求传递闭包。建立邻接矩阵\(f[i][j]\)表示i和j是否有关系,跑一遍floyd,然后检查\(f[i][j]\&f[j][i]\)是否为1。下面给出bitset优化版本,时间复杂度\(O(\frac{n^{3}}{w})\)

1
2
3
4
5
6
7
// std::bitset<SIZ> f[SIZ];
void floyd(int n)
{
for(int k=1; k<=n; k++)
for(int i=1; i<=n; i++)
if(f[i][k]) f[i] = f[i] | f[k];
}

模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
const int inf = 0x3f3f3f3f;
const int mxn = 1e3 + 5;

int g[mxn][mxn], pre[mxn][mxn];

void graph_init(int n)
{
for(int i=1; i<=n; i++){
for(int j=1; j<=n; j++)
g[i][j] = inf;
g[i][i] = 0;
}
}

void floyd_init(int n)
{
for(int i=1; i<=n; i++){
for(int j=1; j<= n; j++){
pre[i][j] = (g[i][j]==inf ? -1 : j);
}
}
}

void floyd(int n)
{
for(int k=1; k<=n; k++)
for(int i=1; i<=n; i++)
for(int j=1; j<=n; j++){
if(g[i][k]+g[k][j] < g[i][j]){
g[i][j] = g[i][k]+g[k][j];
pre[i][j] = pre[i][k];
}
}
}

int main()
{
int n, m;
scanf("%d %d", &n, &m);
graph_init(n);

for(int i=0; i<m; i++)
{
int u, v, w;
scanf("%d %d %d", &u, &v, &w);
g[u][v] = w;
}
floyd_init(n);
floyd(n);

for(int i=1; i<=n; i++){
for(int j=1; j<=n; j++)
printf("%d ", g[i][j]);
printf("\n");
}

for(int i=1; i<=n; i++){
for(int j=1; j<=n; j++)
printf("%d ", pre[i][j]);
printf("\n");
}

return 0;
}

/*
Sample Input:

4 5
1 2 2
1 3 4
2 4 1
3 2 1
3 4 8

Sample Output:

0 2 4 3
1061109567 0 1061109567 1
1061109567 1 0 2
1061109567 1061109567 1061109567 0
1 2 3 2
-1 2 -1 4
-1 2 3 2
-1 -1 -1 4

*/