Floyd-Warshall 算法应用总结

前言

Floyd-Warshall算法多用于求解以下三个问题:

  • 多源最短路,任意两点的距离关系
  • 图上的传递闭包,任意两点的连通关系
  • 最小环路问题
void Floyd(){
	//初始化 
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			dis[i][j]=inf;
			path[i][j]=j;
		}
	}
	//更新最短路径 
	for(int k=1;k<=n;k++)
		for(int i=1;i<=n;i++)
			for(int j=1;j<=n;j++)
				if(a[i][j]>a[i][k]+a[k][j]+b[k]){
					a[i][j]=a[i][k]+a[k][j]+b[k];
					path[i][j]=path[i][k];
				}
	//输出结点u到v的路径
	int tmp=u;
	while(tmp!=v){
		printf("%d ",tmp);
		tmp=path[tmp][v];
	}
	printf("%d\n",v); 					
}

下面再用几个典型的例题来总结一下Floyd算法的简单应用。



求解传递闭包

题意

  • N个人玩一个游戏,每两个人都要进行一场比赛
  • 已知M个胜负关系,每个关系为 A B ,表示 A 比 B 强,胜负关系具有传递性
  • 试问有多少场比赛的胜负无法预先得知

Input

第一行给出数据组数。
每组数据第一行给出 N 和 M(N , M <= 500)。
接下来 M 行,每行给出 A B,表示 A 可以胜过 B。

Output

对于每一组数据,判断有多少场比赛的胜负不能预先得知。注意 (a, b) 与 (b, a) 等价,即每一个二元组只被计算一次。

Sample Input

3
3 3
1 2
1 3
2 3
3 2
1 2
2 3
4 2
1 2
3 4

Sample Output

0
0
4

思路

F l o y d − W a r s h a l l Floyd-Warshall FloydWarshall 算法在求解传递闭包中的应用,根据输入数据创建邻接矩阵 d i s dis dis

  • d i s [ a ] [ b ] = 1 dis[a][b]=1 dis[a][b]=1 表示 a a a b b b
  • d i s [ a ] [ b ] = 0 dis[a][b]=0 dis[a][b]=0 表示 a a a b b b 的胜负关系不明
  • d i s [ a ] [ b ] = 0 dis[a][b]=0 dis[a][b]=0 d i s [ b ] [ a ] = 0 dis[b][a]=0 dis[b][a]=0 表示 a a a b b b 的胜负关系无法预先判断

注意:由于 F l o y d Floyd Floyd 算法求解传递闭包的时间复杂度为 O ( n 3 ) O(n^3) O(n3)为了防止超时,往往再加一步剪枝操作 d i s [ i ] [ j ] = d i s [ i ] [ j ] ∣ ( d i s [ i ] [ k ] dis[i][j]=dis[i][j]|(dis[i][k] dis[i][j]=dis[i][j](dis[i][k]& d i s [ k ] [ j ] ) dis[k][j]) dis[k][j]),在这一步中,若 d i s [ i ] [ k ] dis[i][k] dis[i][k]等于0,那么后边的操作都是多余的,直接剪去即可。另一点需要注意的是三重循环中 i , j , k i,j,k i,j,k 的顺序。

代码实现

#include <iostream>
#include <queue>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <climits>
#include <cstring>
using namespace std;

int cnt,n,m,a,b;
int dis[510][510];

int main()
{
	scanf("%d",&cnt);
	while(cnt--){
		scanf("%d%d",&n,&m);
		memset(dis,0,sizeof(dis));
		for(int i=0;i<m;i++){
			scanf("%d%d",&a,&b);
			dis[a][b]=1;
		}
		for(int k=1;k<=n;k++){
			for(int i=1;i<=n;i++){
				if(!dis[i][k])
					continue;
				for(int j=1;j<=n;j++){
					dis[i][j]=dis[i][j]|(dis[i][k]&dis[k][j]);
				}
			}
		}
		int count=1;
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++){
				if(i==j)
					continue;
				if(dis[i][j]==0&&dis[j][i]==0){
					count++;
				}
			}
		}
		count/=2;
		printf("%d\n",count);
	}
	return 0;
}


多源最短路问题

题目链接

HDU - 1385 Minimum Transport Cost

题意

N 个城市之间进行运输,路过每个城市要交一定量的税,问运输过程中的最小花费(给定多对城市之间的运输)并输出路径。

Input

First is N, number of cities. N = 0 indicates the end of input.

The data of path cost, city tax, source and destination cities are given in the input, which is of the form:

a11 a12 … a1N
a21 a22 … a2N

aN1 aN2 … aNN
b1 b2 … bN

c d
e f

g h

where aij is the transport cost from city i to city j, aij = -1 indicates there is no direct path between city i and city j. bi represents the tax of passing through city i. And the cargo is to be delivered from city c to city d, city e to city f, …, and g = h = -1. You must output the sequence of cities passed by and the total cost which is of the form:

Output

From c to d :
Path: c–>c1–>…–>ck–>d
Total cost : …

From e to f :
Path: e–>e1–>…–>ek–>f
Total cost : …

如果有多条最短路径,则输出字典序最小的那一条路径

Sample Input

5
0 3 22 -1 4
3 0 5 -1 -1
22 5 0 9 20
-1 -1 9 0 4
4 -1 20 4 0
5 17 8 3 1
1 3
3 5
2 4
-1 -1
0

Sample Output

From 1 to 3 :
Path: 1–>5–>4–>3
Total cost : 21

From 3 to 5 :
Path: 3–>4–>5
Total cost : 16

From 2 to 4 :
Path: 2–>1–>5–>4
Total cost : 17

代码实现

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
using namespace std;

const int inf=1e9;
const int maxn=110;
int n=100,m,u,v;
int a[maxn][maxn],b[maxn],path[maxn][maxn];
//path[i][j]保存了从i到j路径的第一个点(除i以外)
int main()
{
	while(~scanf("%d",&n)){
		if(n==0)
			break;
		
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++){
				scanf("%d",&m);
				a[i][j]=(m==-1?inf:m);
				path[i][j]=j;
			}
		}
		for(int i=1;i<=n;i++){
			scanf("%d",&b[i]);
		}
		for(int k=1;k<=n;k++){
			for(int i=1;i<=n;i++){
				for(int j=1;j<=n;j++){
					if(a[i][j]>a[i][k]+a[k][j]+b[k]){
						a[i][j]=a[i][k]+a[k][j]+b[k];
						path[i][j]=path[i][k];
					}
					else if(a[i][j]==a[i][k]+a[k][j]+b[k]){
						path[i][j]=min(path[i][j],path[i][k]);
					}
				}
			}
		}
		while(~scanf("%d%d",&u,&v)){
			if(u==-1&&v==-1)
				break;
			printf("From %d to %d :\n",u,v);
			printf("Path: %d",u);
			int tmp=u;
			while(tmp!=v){
				printf("-->%d",path[tmp][v]);
				tmp=path[tmp][v];
			}
			printf("\nTotal cost : %d\n",a[u][v]);
			printf("\n");
		}	
	}
	return 0;	
}


最小环问题

题目链接

POJ - 1734 Sightseeing trip

题意

N N N 个路口, M M M 条双向道路,两个路口可以和多条道路相连,但没有一条道路可以将一个路口与自己相连。每条观光路线都由 y 1 , … , y k , k > 2 y_1,…, y_k,k>2 y1,,ykk>2 组成,其中 y i ( 1 < = i < = k − 1 ) y_i(1<=i<=k-1) yi(1<=i<=k1) 连接路口 x i x_i xi x i + 1 , y k x_{i+1}, y_k xi+1,yk 连接路口 x k x_k xk x 1 , x 1 , … , x k x_1,x_1,…,x_k x1x1,,xk 应该各不相同,求解观光路线的最短路径,若不存在则输出 N o No No s o l u t i o n . solution. solution.

ps:这道题是 Special Judge 输出环的起点终点顺序无所谓。

Sample Input

5 7
1 4 1
1 3 300
3 1 10
1 2 16
2 3 100
2 5 15
5 3 20

Sample Output

1 3 5 2

思路

  • 环的判断方法: d i s [ i ] [ j ] + a [ i ] [ k ] + a [ k ] [ j ] < I N F dis[i][j]+a[i][k]+a[k][j]<INF dis[i][j]+a[i][k]+a[k][j]<INF
  • 在更新最短路之前,由于此时的 k k k 还没被用于更新最短路 d i s [ i ] [ j ] dis[i][j] dis[i][j],故可以先判断当前的 k k k(对应第一层循环)是否能构成环
  • 若能构成环,则更新最小环的长度并存储路径( i i i j j j的路径 + j − > k + k − > i + j->k + k->i +j>k+k>i

代码实现

#include <iostream>
#include <cstdio>
#include <cstdlib>
using namespace std;

const int inf=1e8;
int n,m,u,v,w,tmp,tot=0;
int a[110][110],dis[110][110],path[110][110],ans[110];
 
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			a[i][j]=dis[i][j]=inf;
			path[i][j]=j;
		}
	}
	for(int i=1;i<=m;i++){
		scanf("%d%d%d",&u,&v,&w);
		a[u][v]=a[v][u]=min(w,a[u][v]);
		dis[u][v]=dis[v][u]=a[u][v];
	}	
	int res=inf;
	for(int k=1;k<=n;k++){
		for(int i=1;i<k;i++){
			for(int j=1;j<i;j++){
				if(dis[i][j]+a[i][k]+a[k][j]<res){
					res=dis[i][j]+a[i][k]+a[k][j];
					tot=0,tmp=i;
					while(tmp!=j){
						ans[tot++]=tmp;
						tmp=path[tmp][j];
					}
					ans[tot++]=j;
					ans[tot++]=k;			
				}
			}
		}	
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++){
				if(dis[i][j]>dis[i][k]+dis[k][j]){
					dis[i][j]=dis[i][k]+dis[k][j];
					path[i][j]=path[i][k];
				}
			}
		}
	}
	if(res==inf)
		printf("No solution.\n");
	else{
		for(int i=0;i<tot;i++){
			printf("%d ",ans[i]);
		}
		printf("\n");		
	}
	return 0;
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值