【转载】坐在马桶上看算法:只有五行的Floyd最短路算法

2023-11-10 15:30

本文主要是介绍【转载】坐在马桶上看算法:只有五行的Floyd最短路算法,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

坐在马桶上看算法:只有五行的Floyd最短路算法

此算法由Robert W. Floyd(罗伯特·弗洛伊德)于1962年发表在“Communications of the ACM”上。同年Stephen Warshall(史蒂芬·沃舍尔)也独立发表了这个算法。Robert W.Floyd这个牛人是朵奇葩,他原本在芝加哥大学读的文学,但是因为当时美国经济不太景气,找工作比较困难,无奈之下到西屋电气公司当了一名计算机操作员,在IBM650机房值夜班,并由此开始了他的计算机生涯。

作者:ahalei来源:51CTO博客|2014-03-26 09:04

暑假,小哼准备去一些城市旅游。有些城市之间有公路,有些城市之间则没有,如下图。为了节省经费以及方便计划旅程,小哼希望在出发之前知道任意两个城市之前的最短路程。
在这里插入图片描述

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

这个问题这也被称为“多源最短路径”问题。

现在需要一个数据结构来存储图的信息,我们仍然可以用一个4*4的矩阵(二维数组e)来存储。

比如1号城市到2号城市的路程为2,则设e[1][2]的值为2。2号城市无法到达4号城市,则设置e[2][4]的值为∞。另外此处约定一个城市自己是到自己的也是0,例如e[1][1]为0,具体如下。
在这里插入图片描述

现在回到问题:如何求任意两点之间最短路径呢?通过之前的学习我们知道通过深度或广度优先搜索可以求出两点之间的最短路径。
所以进行 n 2 n2 n2遍深度或广度优先搜索,即对每两个点都进行一次深度或广度优先搜索,便可以求得任意两点之间的最短路径。可是还有没有别的方法呢?
我们来想一想,根据我们以往的经验,如果要让任意两点(例如从顶点 a a a点到顶点 b b b)之间的路程变短,只能引入第三个点(顶点 k k k),并通过这个顶点k中转即a->k->b,才可能缩短原来从顶点 a a a点到顶点 b b b的路程。

那么这个中转的顶点 k k k是1~ n n n中的哪个点呢?甚至有时候不只通过一个点,而是经过两个点或者更多点中转会更短,即a->k1->k2->b或者a->k1->k2…->k->i…->b

比如上图中从4号城市到3号城市(4->3)的路程e[4][3]原本是12。
如果只通过1号城市中转(4->1->3),路程将缩短为11(e[4][1]+e[1][3]=5+6=11)。
其实1号城市到3号城市也可以通过2号城市中转,使得1号到3号城市的路程缩短为5(e[1][2]+e[2][3]=2+3=5)。
所以如果同时经过1号和2号两个城市中转的话,从4号城市到3号城市的路程会进一步缩短为10。
通过这个的例子,我们发现每个顶点都有可能使得另外两个顶点之间的路程变短。

好,下面我们将这个问题一般化。

当任意两点之间不允许经过第三个点时,这些城市之间最短路程就是初始路程,如下。
在这里插入图片描述

如现在只允许经过1号顶点,求任意两点之间的最短路程,应该如何求呢?

只需判断e[i][1]+e[1][j]是否比e[i][j]要小即可。

e[i][j]表示的是从 i i i号顶点到 j j j号顶点之间的路程。
e[i][1]+e[1][j] 表示的是从 i i i号顶点先到1号顶点,再从1号顶点到 j j j号顶点的路程之和。
其中 i i i是1~ n n n循环, j j j也是1~ n n n循环,代码实现如下。

for(i=1;i<=n;i++)   
{   for(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号顶点的情况下,任意两点之间的最短路程更新为:
在这里插入图片描述

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

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

代码实现为如下:
//经过1号顶点   
for (i = 1; i <= n; i++)for (j = 1; j <= n; j++)if (e[i][j] > e[i][1] + e[1][j])  e[i][j] = e[i][1] + e[1][j];
//经过2号顶点   
for (i = 1; i <= n; i++)for (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号顶点的情况下,任意两点之间的最短路程更新为:
在这里插入图片描述

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

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

允许通过所有顶点作为中转,任意两点之间最终的最短路程为:
在这里插入图片描述

整个算法过程虽然说起来很麻烦,但是代码实现却非常简单,核心代码只有五行:

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

这段代码的基本思想就是:
最开始只允许经过1号顶点进行中转,接下来只允许经过1和2号顶点进行中转……允许经过1~ n n n号所有顶点进行中转,求任意两点之间的最短路程。

用一句话概括就是:从i号顶点到j号顶点只经过前 k k k号点的最短路程。

其实这是一种“动态规划”的思想,关于这个思想我们将在《啊哈!算法2——伟大思维闪耀时》在做详细的讨论。

下面给出这个算法的完整代码:
#include <stdio.h>   
int main()
{int e[10][10], k, i, j, n, m, t1, t2, t3;int inf = 99999999; //用inf(infinity的缩写)存储一个我们认为的正无穷值   //读入n和m,n表示顶点个数,m表示边的条数   scanf("%d %d", &n, &m);//初始化   for (i = 1; i <= n; i++)for (j = 1; j <= n; j++)if (i == j) e[i][j] = 0;else e[i][j] = inf;//读入边   for (i = 1; i <= m; i++){scanf("%d %d %d", &t1, &t2, &t3);e[t1][t2] = t3;}//Floyd-Warshall算法核心语句   for (k = 1; k <= n; k++)for (i = 1; i <= n; i++)for (j = 1; j <= n; j++)if (e[i][j] > e[i][k] + e[k][j])e[i][j] = e[i][k] + e[k][j];//输出最终的结果   for (i = 1; i <= n; i++){for (j = 1; j <= n; j++){printf("%10d", e[i][j]);}printf("\n");}return 0;
}

有一点需要注意的是:如何表示正无穷。
我们通常将正无穷定义为99999999,因为这样即使两个正无穷相加,其和仍然不超过int类型的范围(C语言int类型可以存储的正整数是2147483647)。
在实际应用中估计一下最短路径的上限,只需要设置比它大一点既可以。

例如有100条边,每条边不超过100的话,只需将正无穷设置为10001即可。如果你认为正无穷和其它值相加得到一个大于正无穷的数是不被允许的话,
我们只需在比较的时候加两个判断条件就可以了,请注意下面代码中带有下划线的语句。

//Floyd-Warshall算法核心语句   
for (k = 1; k <= n; k++)for (i = 1; i <= n; i++)for (j = 1; j <= n; j++)if (e[i][k] < inf && e[k][j]<inf && e[i][j]>e[i][k] + e[k][j])e[i][j] = e[i][k] + e[k][j];

上面代码的输入数据样式为:

4 8   
1 2 2   
1 3 6   
1 4 4   
2 3 3   
3 1 7   
3 4 1   
4 1 5   
4 3 12 

行两个数为 n n n m m m n n n表示顶点个数, m m m表示边的条数。
接下来 m m m行,每一行有三个数 t 1 t1 t1 t 2 t2 t2 t 3 t3 t3,表示顶点 t 1 t1 t1到顶点 t 2 t2 t2的路程是 t 3 t3 t3
得到最终结果如下:
在这里插入图片描述

通过这种方法我们可以求出任意两个点之间最短路径。它的时间复杂度是O( N 3 N^3 N3)。
令人很震撼的是它竟然只有五行代码,实现起来非常容易。
正是因为它实现起来非常容易,如果时间复杂度要求不高,使用Floyd-Warshall来求指定两点之间的最短路或者指定一个点到其余各个顶点的最短路径也是可行的。

当然也有更快的算法,请看下一节:Dijkstra算法。

另外需要注意的是:Floyd-Warshall算法不能解决带有“负权回路”(或者叫“负权环”)的图,因为带有“负权回路”的图没有最短路。

例如下面这个图就不存在1号顶点到3号顶点的最短路径。因为1->2->3->1->2->3->…->1->2->3这样路径中,每绕一次1->-2>3这样的环,最短路就会减少1,永远找不到最短路。
其实如果一个图中带有“负权回路”那么这个图则没有最短路。
在这里插入图片描述

此算法由Robert W. Floyd(罗伯特·弗洛伊德)于1962年发表在“Communications of the ACM”上。同年Stephen Warshall(史蒂芬·沃舍尔)也独立发表了这个算法。
Robert W.Floyd这个牛人是朵奇葩,他原本在芝加哥大学读的文学,但是因为当时美国经济不太景气,找工作比较困难,无奈之下到西屋电气公司当了一名计算机操作员,在IBM650机房值夜班,并由此开始了他的计算机生涯。

此外他还和J.W.J. Williams(威廉姆斯)于1964年共同发明了著名的堆排序算法HEAPSORT。堆排序算法我们将在第七章学习。
Robert W.Floyd在1987年获得了图灵奖。

博客地址:http://ahalei.blog.51cto.com/4767671/1383613

这篇关于【转载】坐在马桶上看算法:只有五行的Floyd最短路算法的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/383606

相关文章

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

康拓展开(hash算法中会用到)

康拓展开是一个全排列到一个自然数的双射(也就是某个全排列与某个自然数一一对应) 公式: X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[1]*0! 其中,a[i]为整数,并且0<=a[i]<i,1<=i<=n。(a[i]在不同应用中的含义不同); 典型应用: 计算当前排列在所有由小到大全排列中的顺序,也就是说求当前排列是第

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

【数据结构】——原来排序算法搞懂这些就行,轻松拿捏

前言:快速排序的实现最重要的是找基准值,下面让我们来了解如何实现找基准值 基准值的注释:在快排的过程中,每一次我们要取一个元素作为枢纽值,以这个数字来将序列划分为两部分。 在此我们采用三数取中法,也就是取左端、中间、右端三个数,然后进行排序,将中间数作为枢纽值。 快速排序实现主框架: //快速排序 void QuickSort(int* arr, int left, int rig

poj 3974 and hdu 3068 最长回文串的O(n)解法(Manacher算法)

求一段字符串中的最长回文串。 因为数据量比较大,用原来的O(n^2)会爆。 小白上的O(n^2)解法代码:TLE啦~ #include<stdio.h>#include<string.h>const int Maxn = 1000000;char s[Maxn];int main(){char e[] = {"END"};while(scanf("%s", s) != EO

poj 1511 Invitation Cards(spfa最短路)

题意是给你点与点之间的距离,求来回到点1的最短路中的边权和。 因为边很大,不能用原来的dijkstra什么的,所以用spfa来做。并且注意要用long long int 来存储。 稍微改了一下学长的模板。 stack stl 实现代码: #include<stdio.h>#include<stack>using namespace std;const int M

poj 3259 uva 558 Wormholes(bellman最短路负权回路判断)

poj 3259: 题意:John的农场里n块地,m条路连接两块地,w个虫洞,虫洞是一条单向路,不但会把你传送到目的地,而且时间会倒退Ts。 任务是求你会不会在从某块地出发后又回来,看到了离开之前的自己。 判断树中是否存在负权回路就ok了。 bellman代码: #include<stdio.h>const int MaxN = 501;//农场数const int

poj 1502 MPI Maelstrom(单源最短路dijkstra)

题目真是长得头疼,好多生词,给跪。 没啥好说的,英语大水逼。 借助字典尝试翻译了一下,水逼直译求不喷 Description: BIT他们的超级计算机最近交货了。(定语秀了一堆词汇那就省略吧再见) Valentine McKee的研究顾问Jack Swigert,要她来测试一下这个系统。 Valentine告诉Swigert:“因为阿波罗是一个分布式共享内存的机器,所以它的内存访问

秋招最新大模型算法面试,熬夜都要肝完它

💥大家在面试大模型LLM这个板块的时候,不知道面试完会不会复盘、总结,做笔记的习惯,这份大模型算法岗面试八股笔记也帮助不少人拿到过offer ✨对于面试大模型算法工程师会有一定的帮助,都附有完整答案,熬夜也要看完,祝大家一臂之力 这份《大模型算法工程师面试题》已经上传CSDN,还有完整版的大模型 AI 学习资料,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费