Floyd算法通讲

批判一句

虽然我不知道为什么会有那么多的人在乱讲,但是我无能为力,只能尽量去讲述正确的思路

Floyd标程

Floyd算法,是一个基于dp而成的算法,因为是dp所以一定是从最优解到达最优解,所以网络上的那些什么经过k点转移过去的答案是不可靠的,因为每次转移过去的两个值并不是最优解,所以这样理解肯定是不对的。

因为是dp,我们可以将k定义成经过的点的编号不超过k,也就是说,我们的dp数组应该长成这样
d p [ i ] [ j ] [ k ] / / 从 i 到 j 的路径上的编号不超过 k 的最小值 dp[i][j][k]//从i到j的路径上的编号不超过k的最小值 dp[i][j][k]//ij的路径上的编号不超过k的最小值
如果是这样理解的话,我们转移的时候就有一下两种情况
在这里插入图片描述
那么我们就可以写出一下状态转移方程
d p [ i ] [ j ] [ k ] = d p [ i ] [ j ] [ k − 1 ] / / 如果当前这条路径上的点都小于 k dp[i][j][k]=dp[i][j][k-1] //如果当前这条路径上的点都小于k dp[i][j][k]=dp[i][j][k1]//如果当前这条路径上的点都小于k
d p [ i ] [ j ] [ k ] = d p [ i ] [ k ] [ k − 1 ] + d p [ k ] [ j ] [ k − 1 ] / / 因为有点 k ,所以以点 k 为分界点,将左右两边的最优解加起来 dp[i][j][k]=dp[i][k][k-1]+dp[k][j][k-1] //因为有点k,所以以点k为分界点,将左右两边的最优解加起来 dp[i][j][k]=dp[i][k][k1]+dp[k][j][k1]//因为有点k,所以以点k为分界点,将左右两边的最优解加起来
注意:这里的把看作为转换点和网络上的是不一样的,这里是有限制的。
所以我们把这两个状态转移方程合并一下就可以得到以下内容
d p [ i ] [ j ] [ k ] = m i n ( d p [ i ] [ j ] [ k − 1 ] , d p [ i ] [ k ] [ k − 1 ] + d p [ k ] [ j ] [ k − 1 ] ) dp[i][j][k]=min(dp[i][j][k-1],dp[i][k][k-1]+dp[k][j][k-1]) dp[i][j][k]=min(dp[i][j][k1],dp[i][k][k1]+dp[k][j][k1])
此时我们的边界条件是 d p [ i ] [ j ] [ 0 ] = m p [ i ] [ j ] dp[i][j][0]=mp[i][j] dp[i][j][0]=mp[i][j](不存在的边存为无无穷大)
可以发现,我们只做了一次dp,并且k的值是递增的,所以我们只需要开一个二维数组就行了,这里有点像滚动数组。

示范代码

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

最小环与Floyd

如果我们要在一个图中,找到一个至少有3个节点的环,那么我们可以怎么办呢?
考虑Floyd算法的过程,因为 d i s [ i ] [ j ] dis[i][j] dis[i][j] 在外层循环的时候,是存储的“经过编号不超过 k − 1 k-1 k1 的结点,从 i i i j j j 的最短路的长度,因此我们就可以想到 m i n ( d i s [ i ] [ j ] + a [ j ] [ k ] + a [ k ] [ i ] ) ( 1 ≤ i < j < k ) min(dis[i][j]+a[j][k]+a[k][i])(1 \le i <j <k) min(dis[i][j]+a[j][k]+a[k][i])(1i<j<k)就是满足以下两个条件的最小环的长度:

  • 由编号不超过k的节点构成
  • 经过了节点k

注:上述的 i , j i,j i,j枚举了与 k k k相邻的两个点
这里有一个易错点,为什么是 d i s [ i ] [ j ] + a [ j ] [ k ] + a [ k ] [ i ] dis[i][j]+a[j][k]+a[k][i] dis[i][j]+a[j][k]+a[k][i]而不是 d i s [ i ] [ j ] + d i s [ j ] [ k ] + d i s [ k ] [ i ] dis[i][j]+dis[j][k]+dis[k][i] dis[i][j]+dis[j][k]+dis[k][i]
客观的说,我刚开始也没搞懂,其实这里画个图就行了。
在这里插入图片描述
因为我们开的是滚动数组,所以我们需要在进行Floyd的同时,计算最小环,也就是说,我们要把计算最小的代码放在计算Floyd的前面,因为我需要用的是所经过的节点的编号不超过k-1的值,如果放在后面的话,就变成计算节点编号不超过k的值了。

示范代码

#include<bits/stdc++.h>
using namespace std;
const long long INF=1e13;
long long a[310][310],dis[310][310];
long long n,m,ans=INF;
int main(){
	cin>>n>>m;
	memset(dis,0x3f3f,sizeof(dis));
	memset(a,0x3f3f,sizeof(a));
	for (int i=0;i<=n+5;i++){
		a[i][i]=0;
		dis[i][i]=0;
	}
	for (int i=1;i<=m;i++){
		long long x,y,z;
		cin>>x>>y>>z;
		a[x][y]=a[y][x]=min(a[x][y],z);
		dis[x][y]=dis[y][x]=a[x][y];
	}
	for (int k=1;k<=n;k++){
		//计算最小环 
		for (int i=1;i<k;i++){//因为是计算编号数小于等于k-1的结点,所以是i<k 
			for (int j=i+1;j<k;j++){
				ans=min(dis[i][j]+a[i][k]+a[k][j],ans);
			}
		}
		//计算Floyd 
		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]);
			}
		}
	}
	if (ans==INF)cout<<"No solution.";
	else cout<<ans;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值