算法学习笔记(28)——Floyd算法(多源汇最短路)
Floyd 算法
Floyd 算法用于多源汇最短路问题。时间复杂度为 \(O(n^3)\) 。
算法思想:
首先用邻接矩阵里的d[i][j]
存储所有的边(重边的时候取min
),然后就是三重循环,思路也是如果从i
到k
,再从k
到j
,这个距离(d[i][k] + d[k][j]
)能比d[i][j]
小,就更新一下:d[i][j] = min(d[i][j], d[i][k] + d[k][j])
。
需要注意的是三重循环的顺序,先循环中间位置k
,再循环源点i
,最后循环汇点j
。
初始化时,d[i][j]
存储的是从i
到j
的最短边,算法执行结束后d[i][j]
就是从i
到j
的最短距离。
由于存在负权边,如果d[i][j] = INF
,d[i][k] = INF
,而d[k][j] < 0
,那么d[i][j]
实际是不可达但也会被更新,所以此时我们不能通过与INF
比较来判断是不是可达的,选择一个较大的数(通常是INF/2
)进行比较判断,大于该阈值则判为不可达。
Floyd算法属于动态规划算法。
原理分析:
状态表示:f[k][i][j]
表示从i
点出发,只经过1~k
这些中间点,最后到达j
点的最短距离。
那么我们就可以根据第k
个点选或不选进行状态转移
- 如果不选第
k
个点:f[k-1][i][j]
,表示从i
到j
经过了1~k-1
这些点。 - 如果选第
k
个点:只能选择一次,否则最短路存在环。则状态表示为f[k-1][i][k] + f[k-1][k][j]
,意味着先从i
到k
经过了1~k-1
这些中间点,然后经过1~k-1
这些中间点从k
到达j
。
转移方程可以表示为:
\[f[k][i][j] = min(f[k-1][i][j], f[k-1][i][k] + f[k-1][k][j])
\]
由于转移方程左侧是k
,右侧是k-1
,所以只要从小到大枚举k
即可将这一维度省去,也就演变成了d[i][j]
。
#include <iostream>
using namespace std;
const int N = 210, INF = 0x3f3f3f3f;
int n, m, k;
int d[N][N]; // 邻接矩阵存储图
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]);
}
int main()
{
cin >> n >> m >> k;
// 初始化邻接矩阵
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 x, y, z;
cin >> x >> y >> z;
d[x][y] = min(d[x][y], z); //两个点之间只存最短的那条边
}
floyd();
while (k -- ) {
int x, y;
cin >> x >> y;
if (d[x][y] > INF / 2) puts("impossible");
else cout << d[x][y] << endl;
}
return 0;
}