Floyd 算法本质是动态规划
基本要点
- 时间复杂度是 O(n^3),因此,只适用于n<500 的这种结点数不多的情况。
- 它是多源最短路的,可以求出任何两点之间的最短路。
- 支持负边权
- 能够判断图中是否存在负回路
代码:
#include<bits/stdc++.h>
using namespace std;
int g[N][N];
int dis[N][N]; // dis[i][j]: i->j 最短距离
/*
证明:定义 dis[i][j][k] 表示从 i 到 j 的可以经过 1~k 号结点的最短路径,此问题可以被分解为两个子问题
(1)不经过 k 号结点,即 dis[i][j][k-1]
(2)经过 k 号结点,即 dis[i][k][k-1]+dis[k][j][k-1];
因此,有:
dis[i][j][k] = min(dis[i][j][k-1], dis[i][k][k-1]+dis[k][j][k-1]);
上式中第三维 k 可以省略,则有:
dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);
虽然 i,j,k 的顺序不保证,导致上式中 dis[i][k] 和 dis[k][j] 可能是已经被更新过的 dis[i][k][k] 和 dis[k][j][k] 了,
但是这并不影响其正确性。因为更新后的值一定会变得更小,不会影响最终结果。
*/
int main()
{
int n;
cin >> n;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
{
cin >> g[i][j];
dis[i][j] = g[i][j];
}
// floyd 求任意两点间的最短路径
for(int k = 1; k <= n; k++) // 枚举中间点
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n;j++)
dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);
// 判断负环
for(int i =1; i <= n; i++)
{
if(dis[i][i] < 0) {
cout<<"存在负环";
break;
}
}
return 0;
}
Copy
证明:
定义 dis[i][j][k]表示从 i 到 j的可以经过 1~k 号结点的最短路径,此问题可以被分解为两个子问题
- 不经过 k 号结点,即 dis[i][j][k-1]
- 经过 k 号结点,即 dis[i][k][k-1]+dis[k][j][k-1]
因此,有:
dis[i][j][k] = min(dis[i][j][k-1], dis[i][k][k-1]+dis[k][j][k-1]);
上式中第三维 k 可以省略,则有:
dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);
虽然去掉一维后,i,j,k 的顺序不保证,导致上式中 dis[i][k] 和 dis[k][j] 可能是已经被更新过的 dis[i][k][k]和 dis[k][j][k] 了,但是这并不影响其正确性。因为更新后的值一定会变得更小,不会影响最终结果。
Floyd 算法为什么把k放在最外层?
采用动态规划思想,f[k][i][j] 表示 i和 j 之间可以通过编号不超过 kk,即编号 1,2,...,k的节点的最短路径。初值 f[0][i][j]为原图的邻接矩阵。
则该问题可以划分为两个子问题:
-
f[k][i][j] 可以从 f[k-1][i][j]转移来,表示 i到 j不经过 k 这个节点。
-
也可以从 f[k-1][i][k] + f[k-1][k][j]转移过来,表示经过 k 这个点。
于是:f[k][i][j]=min(f[k−1][i][j],f[k−1][i][k]+f[k−1][k][j])
然后你就会发现 f 最外层一维空间可以省略,因为 f[k][][] 只与 f[k-1][][]有关,虽然省略后,无法确定 k,i,j的关系,但是 f[k][i][k]和 f[k][k][j] 是由 f[k-1][i][k]和 f[k-1][k][j] 计算而来,因此前者一定小于等于后者,省略后再取最小值并不会影响最终结果的正确性。