Floyd-Warshall算法

 Floyd算法是求解任意两点之间的最短路的算法(多源最短路问题)。它的代码长度很短,只有五行,可是算法复杂度很高,可以在O(|n^3|)时间里求得所有两点间的最短路长度,所以对于三位数一下的最短路题目,可以考虑使用Floyd算法,简单实用。Floyd也可以处理负数的情况,而且可以判断图中是否有负图。

 小哼准备去几个城市去旅游,有些城市之间有公路,有些城市之间没有,如下图,为了节省花费以及方便计划旅程,小哼希望出发之前知道两个城市之间的最短路径。


上图中有4个城市,八条公路,公路上的数字表示这条公路的长短,公路都是单向的。现在我们需要求任意两个城市之间的最短路径,也就是求任意两点之间的最短路。

我们现在用一个4*4的矩阵(二维数组e)来存图,比如1号城市到2号城市的路程为2,则设e[1][2]的值为2。2号城市无法到达4号城市,则设置e[2][4]的值为inf,另外一个城市自己到自己的路程为0,即e[i][i] = 0。

城市1234
10264
2inf03inf
37inf01
45inf120
我们想一想根据以往的经验,如果要让任意两点(例如从顶点a到顶点b)之间的路程变短,只能引入第三个点(顶点k),并通过这个顶点k中转即a->k->b,才能缩短原来从顶点a到顶点b的路程。那么这个中转的顶点k是1~n中的哪一个点呢?甚至有时候不只通过一个点,而是经过两个点或者更多点中转会更短,即a->k1->k2->ki->...->b。比如上图中从4号城市到3号城市(4->3)的路程,e[4][3]原本是12,如果只通过1号城市中转(4->1->3),路程将缩短为11(e[4][1]+e[1][3]=5+6=11)。如果通过1号城市和2号城市中转的话,从4号城市到3号城市的路程将会进一步缩短为10(e[4][1]+e[1][2]+e[2][3]=5+2+3=10)。通过这个例子,我们发现每个顶点都有可能使得另外两个顶点之间的路程变短。

当任意两点之间不允许经过第三个点时,这些城市之间的最短路程就是初始路程。如下图所示:

城市1234
10264
2inf03inf
37inf01
45inf120

假如现在只允许经过1号顶点,求任意两个点之间的最短路程。只需要判断e[i][1] + e[1][j]是否比e[i][j]小即可。e[i][j]表示的是从i号顶点到j号顶点之间的路程。e[i][1]+e[1][j]表示的是从i号顶点先到1号顶点,再从1号顶点到j号顶点的路程之和。代码实现如下

for(int i = 1; i <= n; i++) {
	for(int j = 1; j <= n; j++) {
		if(e[i][j] > e[i][1] + e[1][j])
			e[i][j] = e[i][1] + e[1][j];
	}
}

如果只允许经过1号顶点的情况下,任意两点之间的最短路程为:

城市1234
10264
2inf03inf
37901
457110

通过上图我们发现:在只通过1号顶点中转的情况下,3号顶点到2号顶点(e[3][2]),4号顶点到2号顶点(e[4][2])以及4号顶点到3号顶点(e[4][3])的路程都变短了。

接下来继续求在只允许经过1和2号两个顶点的情况下任意两点之间的最短路径。然后我们只允许经过1号顶点时任意两点的最短路程的结果下,再判断如果经过2号顶点是否可以使得i号顶点到j号顶点之间的路程变短,即同上我们只需要比较e[i][j]和e[i][2]+e[2][j],代码如下:

for(int i = 1; i <= n; i++) {
	for(int j = 1; j <= n; j++) {
		if(e[i][j] > e[i][2] + e[2][j])
			e[i][j] = e[i][2] + e[2][j];
	}
}

在只允许经过1和2号顶点的情况下,任意两点之间的最短路程更新为:

城市1234
10254
2inf03inf
37901
457100

通过上图得知,在相比只允许通过1号顶点进行中转的情况下,这里允许通过1和2号顶点进行中转,使得e[1][3]和e[4][3]的路程变得更短

同理,继续在只允许经过1,2,3号顶点进行中转的情况下,求任意两点之间的最短路程。任意两点之间的最短路程更新为:

城市1234
10254
210034
37901
457100

最后允许通过所有顶点作为中转,任意两点之间最终的最短路程为:

城市1234
10254
29034
36801
457100

核心代码只有五行:

for(int k = 1; k <= n; k++) {
	for(int i = 1; i <= n; i++) {
		for(int j = 1; j <= n; j++) {
			if(e[i][j] >= e[i][k] + e[k][j])
				e[i][j] = e[i][k] + e[k][j];
		}
	}
}
具体代码如下:

#include <stdio.h>
#define inf 0x3f3f3f3f
int e[105][105];
int main() {
	int n, m;//代表n个点,m条边 
	int t1, t2;//求t1到t2的距离 
	int u, v, w;
	scanf("%d %d", &n, &m);
	scanf("%d %d", &t1, &t2);
	//初始化 
	for(int i = 1; i <= n; i++) {
		for(int j = 1; j <= n; j++) {
			if(i == j) e[i][j] = 0;
			else e[i][j] = inf;
		}
	}
	//读入边 
	for(int i = 1; i <= m; i++) {
		scanf("%d %d %d", &u, &v, &w);
		e[u][v] = w;//单向边,如果双向就再加上:e[v][u]=w; 
	}
	//Floyd-Warshall核心算法
	for(int k = 1; k <= n; k++) {
		for(int i = 1; i <= n; i++) {
			for(int j = 1; j <= n; j++) {
				if(e[i][j] > e[i][k] + e[k][j])
				e[i][j] = e[i][k] + e[k][j];
			}
		}
	}
	//输出t1到t2的最短距离
	printf("%d\n", e[t1][t2]);
	return 0; 
}

以上内容大多参考《啊哈算法》。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值