介绍
Floyd算法是一种求多源汇最短路的算法,它可以求出任意两点间的最短距离(如果这两点连通的话),并且Floyd算法非常容易实现:
算法模板 O ( n 3 ) O(n^3) O(n3)
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]);
它总共有3
层循环,每层n
次,所以时间复杂度为
O
(
n
3
)
O(n^3)
O(n3)
Floyd算法的基本应用
例题
题目描述
给定一个n
个点m
条边的有向图,图中可能存在重边
和自环
,边权可能为负数
。
再给定k
个询问,每个询问包含两个整数x
和y
,表示查询从点x
到点y
的最短距离,如果路径不存在,则输出impossible
。
数据保证图中不存在负权回路。
输入格式
第一行包含三个整数n
,m
,k
。
接下来 m 行,每行包含三个整数 x,y,z,表示存在一条从点x到点y的有向边,边长为z。
接下来 k 行,每行包含两个整数 x,y,表示询问点x到点y的最短距离。
输出格式
共 k 行,每行输出一个整数,表示询问的结果,若询问两点间不存在路径,则输出impossible
。
数据范围
1
≤
n
≤
200
1≤n≤200
1≤n≤200,
1
≤
k
≤
n
2
1≤k≤n^2
1≤k≤n2
1
≤
m
≤
20000
1≤m≤20000
1≤m≤20000,
图中涉及边长绝对值均不超过
10000
10000
10000。
输入样例:
3 3 2
1 2 1
2 3 2
1 3 1
2 1
1 3
输出样例:
impossible
1
算法设计
本题题意很简单,给定k个询问,每个询问给出两个点,让我们输出这两点的最短距离,如果这两点不在同一连通块就输出impossible
这正完美契合了Floyd算法,因此我们只要将细节处理一下即可(由于Floyd模板中用到了k,因此代码会将此处的k换成Q)
代码初始化:将
d
[
i
,
i
]
d[i,i]
d[i,i]都初始化为0,其余初始化为+∞,因为从i到i的距离是为0的,要求最小值,所以其它距离刚开始时要为∞,后续才能顺利地更新
代码
#include<iostream>
using namespace std;
const int N = 210,M = 20010,INF = 0x3f3f3f3f;
int n,m,Q;
int d[N][N]; // d[i][j] 表示从i到j的距离
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(){
scanf("%d%d%d",&n,&m,&Q);
// 初始化
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 a,b,c;
scanf("%d%d%d",&a,&b,&c);
d[a][b] = min(d[a][b],c); // 两点之间如果有多条边,那么选最短的边即可
}
// floyd
floyd();
// 处理询问
while(Q --){
int a,b;
scanf("%d%d",&a,&b);
if(d[a][b] > INF / 2) puts("impossible"); // 边权可能有负数,所以不会直接等于INF
else printf("%d\n",d[a][b]);
}
return 0;
}
Floyd算法的证明
说是证明,其实不如说是原因,为什么Floyd算法的代码实现是这样的呢?(因为我是先学的板子才学的为什么,所以这里也姑且这么写)
这一切都要从动态规划开始说起。
用闫氏DP分析法:
-
状态表示:我们用 d [ k , i , j ] d[k,i,j] d[k,i,j]表示从i走到j,且中间经过的点的编号不超过k的所有路径长度的最小值,什么叫做中间经过点的编号不超过k呢?假设
i
到j
中间经过所有点为 a 1 , a 2 , . . . , a m a_1,a_2,...,a_m a1,a2,...,am,那么这m个点的编号值都 < = k <=k <=k,注意不要与总共经过不超过k个点混淆。 -
状态计算: d [ k , i , j ] d[k,i,j] d[k,i,j]的计算可以分为两类:
-
从i走到j,且中间经过的点的编号不超过k且不包含k的所有节点路径:那么它就等价于从i走到j且经过的点的编号不超过k - 1的所有路径的最小值,即 d [ k − 1 , i , j ] d[k - 1,i,j] d[k−1,i,j]
-
从i走到j,且中间经过的点的编号不超过k且包含k的所有节点路径:那么我们就会发现,它一定会从i走到k,并且一定会从k走到j,因此我们可以将它分为两段之和:
- 从i走到k,且中间经过的点的编号不超过k - 1的所有节点路径的最小值,即 d [ k − 1 , i , k ] d[k - 1,i,k] d[k−1,i,k]
- 从k走到j,且中间经过的点的编号不超过k - 1的所有节点路径的最小值,即 d [ k − 1 , k , j ] d[k - 1,k,j] d[k−1,k,j]
并且前后两段是没有关系的,所以这种情况的表示就是 d [ k − 1 , i , k ] + d [ k − 1 , k , j ] d[k - 1,i,k] + d[k - 1,k,j] d[k−1,i,k]+d[k−1,k,j]
因此,我们可以得出 d [ k , i , j ] = m i n ( d [ k − 1 , i , j ] , d [ k − 1 , i , k ] + d [ k − 1 , k , j ] ) d[k,i,j] = min(d[k - 1,i,j],d[k - 1,i,k] + d[k - 1,k,j]) d[k,i,j]=min(d[k−1,i,j],d[k−1,i,k]+d[k−1,k,j])
-
-
优化:我们在01背包中已经知道DP问题的状态转移方程可以降维,那么此刻我们发现状态转移方程的第一维只和 k , k − 1 k,k - 1 k,k−1有关,那么能否把它也降维呢?
只需要看看当计算到 d [ k , i , j ] d[k,i,j] d[k,i,j]时使用的k是当前的k还是上一个k即可,此时我们考虑特殊情况:当k = j
时, d [ k , i , j ] = m i n ( d [ k − 1 , i , j ] , d [ k − 1 , i , j ] + d [ k − 1 , j , j ] ) d[k,i,j] = min(d[k - 1,i,j],d[k - 1,i,j] + d[k - 1,j,j]) d[k,i,j]=min(d[k−1,i,j],d[k−1,i,j]+d[k−1,j,j]),由于刚开始初始化时我们将从i到i的距离都初始化为0
了,因此状态转移等价于 d [ k , i , j ] = m i n ( d [ k − 1 , i , j ] , d [ k − 1 , i , j ] + 0 ) d[k,i,j] = min(d[k - 1,i,j],d[k - 1,i,j] + 0) d[k,i,j]=min(d[k−1,i,j],d[k−1,i,j]+0)相当于没有运算,因此每次的 d [ k , i , j ] d[k,i,j] d[k,i,j]都必然是第k - 1
层的 d [ k − 1 , i , j ] d[k - 1,i,j] d[k−1,i,j],所以可以将第一维去掉,最终状态转移方程为 d [ i , j ] = m i n ( d [ i , j ] , d [ i , k ] + d [ k , j ] ) d[i,j] = min(d[i,j],d[i,k] + d[k,j]) d[i,j]=min(d[i,j],d[i,k]+d[k,j])
Floyd算法的扩展应用
Floyd可以应用于许多方面。
- 求多源汇最短路
- 求传递闭包
- 找最小环
- 求恰好经过k条边的最短路(用倍增思想)