C++ 图论之Floyd算法求解次最短路径的感悟,一切都是脱壳后找最值而已

公众号:编程驿站

1. 前言

抛开基因的影响,学霸和学渣到底是在哪一点上有差异?

学霸刷完 200 道题,会对题目分类,并总结出解决类型问题的通用模板,我不喜欢模板这个名词,感觉到投机的意味,或许用方法或通用表达式更高级一点。而事实上模板一词更准确。

每一道题目在描述时,会套上一堆场景说词,可以说是契合真正的应用领域,或者说是出题人的故弄玄虚,弄了一些花里胡哨的迷糊你的外表,这时考核的不是专业知识,而是语文阅读能力。一旦脱出外壳,露出来的底层需求,就是书本上最基础的知识。

小学生学乘法表后,老师会布置很多应用题,不管应用题目的描述如何变化,一旦语文阅读理解过关,剩下的就是套用九九乘法表。为什么学霸学起来一直很轻松原因就在这里,做道时看山不是山。而学渣总是认为一道题目就是一个新知识,疲于学习,永无止境地追赶,停留在看山是山的境界。

为什么说这些?

这段时间写最小生成树、次最小生成树以及最短路径和次最短路径。总结一下,应该不难发现。本质就是在群体数据中找最小值和次最小值,这是最最基础的最值算法思想。如果是在一维数组中找最大值、最小值,只要有点语言基础的都能解决。

代码结构如下:

#include <bits/stdc++.h>
using namespace std;
int main() {
	//一维数组
	int nums[5]= {5,3,1,8,2};
	//存储最大值
	int maxVal_1=nums[0];
	//存储最小值
	int maxVal_2=nums[0];
	//遍历数组
	for(int i=0; i<5; i++) {
		//是否大于最大值
		if( nums[i]>maxVal_1 ) {
			//原来的最大值必然退居成第二大值
			maxVal_2=maxVal_1;
			//如果大于最大值,必然成为最大值
			maxVal_1=nums[i];
		} else if(nums[i]>maxVal_2) {
			//如果大于第二大的值,成为第二大值。
			maxVal_2=nums[i];
		}
	}
	cout<<maxVal_1<<"\t"<<maxVal_2<<endl;
	return 0;
}

其中玄机很简单。公司里空降了一位新领导,如果级别比现任的最高领导的级别高。则现任最高领导成为二把手,新领导成为一把手。如果新领导只比公司现任的二把手高,则挤掉二把手,成为新的二把手。

找最值算法本质,确定一个值,然后查找是否有比此值更大或更小的值,多重选择而已。

当问题变成找最小生成树,次最小生成树、最短路径,次最短路径时……

算法的思想本质没有发现变化,只是遍历的对象变成了图结构或树结构。虽然算法的底层策略不变,但因图结构比线性结构复杂的多,遍历过程中面临的选择也增多,如何选择,如何存储就变得稍难一点。

最短路径常用的算法为Floyd、Bellman、SPFA、Dijkstra。既然能找出最短路径,当然是能找出次最短路径的。下面将分别使用Floyd算法,细聊如何找出次最短路径。

2. Floyd算法

Floyd算法不甚了解的读者可以查阅本公众号里的《C++图论之常规最短路径算法的花式玩法(Floyd、Bellman、SPFA、Dijkstra算法合集)》一文。

使用Floyd算法求解最短路径时,顺手也能求解出次最短路径,下面捋捋这个过程。以下面的图结构为案例。

1.png

邻接矩阵存储存初始时,节点之间的权重关系。0表示自己和自己的距离,INF表示两节点间无直接连接,数值表示两节点的连接权重。Floyd是多源最短路径算法。算法结果需要记录任意两点间的距离,二维数组是较佳的选择。

现在除了要求解最短路径,还需要求解出次最短路径。则有两种存储方案:

  • 三维数组。
  • 两个二维数组。

三维数组本质是多个二维数组在空间深度上的叠加。如下图,所有二维数组的ij坐标描述任意两个节点的编号。z坐标表示两个节点之间的第一最短距离、第二最短距离、第三最短距离……

10.png

演示算法流程时,借助于两个二维数数组更易于表达。如下图所示,初始,最短距离为两点间的权重值,次最短距离为INF(无穷大)

Tips:次最短距离有严格次最短距离,要求次最短距离必须小于最短距离。非严格次最小距离,则可以是大于或等于最短距离。

11.png

Floyd算法的特点是通过在任意两点间插入一个节点,检查是否能缩短其距离。如选择节点1做为中插入点,检查其它任意点之间是否可以通过此点缩短其距离。

graph_1[3][4]原来的值为INF,经过中转点后值为graph_1[3][1]+graph_1[1][4]=10,大于原来的最短距离,则原来的最短距离变成第二短距离,经过中转后的值为新的最短距离。

12.png

以此类推,分别计算出其它两点经过1号节点后的最短距离和次最短距离。

3-5原来最短距离是1,如果经过1号节点则距离为graph_1[3][1]+graph_[1][5]=12。大于原始值但是小于次最短距离,故,最短距离不更新,次最短距离更新为12

12.png

一维数组中的选择是线性的,图结构中的选择复杂。Floyd算法提供插入这个选择理念,底层最值的算法思想没有发生任何本质上的变化。新老大了,原老大退居第二;新老二来了,原老二退居第三……

其它演示流程不再展现,直接上代码。

#include <bits/stdc++.h>

using namespace std;
//最短路径
int graph_1[100][100][2];
map<pair<int,int>,vector<int>> paths;
//节点数、边数
int n,m;
//无穷大
const int INF=999;
//初始化图,自己和自己的距离为0,和其它节点距离为 INF
void init() {
	for(int i=1; i<=n; i++) {
		for(int j=1; j<=n; j++) {
			if(i==j) {
				graph_1[i][j][0]=0;
				graph_1[i][j][1]=0;
			} else {
				graph_1[i][j][0]=INF;
				graph_1[i][j][1]=INF;
			}
		}
	}
}

//交互式得到节点之间关系
void read() {
	int f,t,w;
	for(int i=1; i<=m; i++) {
		cin>>f>>t>>w;
		graph_1[f][t][0]=w;
		//无向图
		graph_1[t][f][0]=w;
	}
}
//Floyd算法
void  floyd() {
	//核心代码
	for(int dot=1; dot<=n; dot++) {
		//以每一个点为插入点,然后更新图中任意两点以此点为中转时的路线权重
		for(int  i=1; i<=n; i++) {
			for(int j=1; j<=n; j++) {
				//经过中转后的权重是否小于原来权重
				if( graph_1[i][dot][0]+graph_1[dot][j][0] <graph_1[i][j][0]  ) {

					graph_1[i][j][1]=graph_1[i][j][0];
					graph_1[i][j][0] =graph_1[i][dot][0]+graph_1[dot][j][0];

				} else if(  graph_1[i][dot][0]+graph_1[dot][j][0]>graph_1[i][j][0] &&    graph_1[i][dot][0]+graph_1[dot][j][0] <graph_1[i][j][1]  ) {
						graph_1[i][j][1]=graph_1[i][dot][0]+graph_1[dot][j][0];

				}
			}
		}
	}
}
//输入矩阵中信息
void show() {
	for(int i=1; i<=n; i++) {
		for(int j=1; j<=n; j++)
			cout<<graph_1[i][j][0]<<"-"<<graph_1[i][j][1]<<"\t";
		cout<<endl;
	}
}

int main(int argc, char** argv) {
	cin>>n>>m;
	init();
	read();
	floyd();
	show();
	return 0;
}

测试数据:

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

测试结果:

13.png

有问题的代码

如图所示,1-3的最短距离是4,直观可以判断正确性。但是1-3的次最短距离是6,可能会让你有点莫名其妙。因为直观上讲,应该是9,也就1-5-3这条路径,除此之外,似乎没有比这个值更合理的。

6这个值是怎么来的?

14.png

算法的计算逻辑是把1-3的路径分解成1-53-5,因1-5的之间的最短路径是1-3-5值为5。所以,最后的结果是1-5的最短路径值加上3-5之间的最短路径值,结果为6。如下图演示效果。

15.png

如何解决这个问题?

先跑一次Floyd算法,得到任意两点间的距离,再删除任意两点之间的最短路径上的边,再跑一次Floyd算法,便可求解出次最短路径。

如在求解1-2的最短路径时,记录最短路径的整个路径链1-3-5-2,然后试着删除1-3跑一次,再删除3-5跑一次,再删除5-2走一次,最后在三次中选择最1-2之间的最短距离。

代码如下:

#include <bits/stdc++.h>
using namespace std;
//图
int graph[100][100];
//最短路径
int paths[100][100];
//节点数、边数
int n,m;
//记录任意两点间最短距离所比对过的节点
map<pair<int,int>,vector<int>> prec;
//无穷大
const int INF=999;
//初始化图,自己和自己的距离为0,和其它节点距离为 INF
void init() {
	for(int i=1; i<=n; i++) {
		for(int j=1; j<=n; j++) {
			if(i==j)graph[i][j]=paths[i][j]=0;
			else graph[i][j]=paths[i][j]=INF;
		}
	}
}
//交互式得到节点之间关系
void read() {
	int f,t,w;
	for(int i=1; i<=m; i++) {
		cin>>f>>t>>w;
		graph[f][t]=paths[f][t]=w;
		//无向图
		graph[t][f]=paths[t][f]=w;
	}
}
//Floyd 最短路径算法
void  floyd() {
	//核心代码
	for(int dot=1; dot<=n; dot++) {
		//以每一个点为插入点,然后更新图中任意两点以此点为中转时的路线权重
		for(int  i=1; i<=n; i++) {
			for(int j=1; j<=n; j++) {
				//经过中转后的权重是否小于原来权重
				if( paths[i][dot]+paths[dot][j] <paths[i][j]  ) {
					paths[i][j] =paths[i][dot]+paths[dot][j];
					//记录任意两点之间的节点
					pair<int,int> p= {i,j};
					vector<int> v=prec[p];
					v.push_back(dot);
					prec[p]=v;
				}
			}
		}
	}
}
//次最短路径算法
void floyd(int i,int j) {
	//恢复图原来数据
	for(int i1=1; i1<=n; i1++) {
		for(int j1=1; j1<=n; j1++) {
			paths[i1][j1]=graph[i1][j1];
		}
	}
	//删除最短路径上的边
	paths[i][j]=INF;
   //路一次算法
	for(int dot=1; dot<=n; dot++) {
		//以每一个点为插入点,然后更新图中任意两点以此点为中转时的路线权重
		for(int  i=1; i<=n; i++) {
			for(int j=1; j<=n; j++) {
				//经过中转后的权重是否小于原来权重
				if( paths[i][dot]+paths[dot][j] <paths[i][j]  ) {
					paths[i][j] =paths[i][dot]+paths[dot][j];
				}
			}
		}
	}
}

//输出矩阵中信息
void show() {
	for(int i=1; i<=n; i++) {
		for(int j=1; j<=n; j++)
			cout<<paths[i][j]<<"\t";
		cout<<endl;
	}
}

int main(int argc, char** argv) {
	cin>>n>>m;
	init();
	read();
	floyd();
	show();

	int i=1,j=2;
	cin>>i>>j;
	int i1=i;
	pair<int,int> p= {i,j};
	vector<int> v=prec[p];
	int res=INF;
	for(int k=0; k<v.size(); k++) {
		floyd(i1,v[k]);
		res=min(res,paths[i][j]);
		i1=v[k];
	}
	floyd(i1,j);
	res=min(res,paths[i][j]);
	cout<<i<<"-"<<j<<" "<<res<<endl;
	return 0;
}

测试1-2之间的最短路径。

16.png

时间复杂度分析:一句话,时间复杂度有点感人,性能堪忧,至于如何优化就留给聪明的你了。

3. 最小环

图中最小环的问题,可以使用Floyd算法求解。算法流程:

  • 跑一次算法,得到任意两点间的最短距离。
  • 检查任意两点之间的最短距离是否有其它节点存在(环至少需要 3 个点),如这两点之间有连接,可证明这两点间有环。
  • 求解最小环。

如下图所示,1-2之间的最短路径链为1-3-5-2。因为1-2之间没有直接相连的边,说明这个最短路径不构成环。3-2最短路径线为3-5-2,且3-2之间有边相连,证明2-5-3这条最短路径存在环,且此环的权重和为10;如1-5的最短距离为1-3-5,且是一个环,权重和为13。至于最小环是谁,只有找出所有环且计算它们的权重和后方可知。

17.png

编码实现:

#include <bits/stdc++.h>

using namespace std;
//图
int graph[100][100];
//最短路径
int paths[100][100];
//节点数、边数
int n,m;
//记录任意两点间最短距离所比对过的节点
map<pair<int,int>,vector<int>> prec;
//无穷大
const int INF=999;
//初始化图,自己和自己的距离为0,和其它节点距离为 INF
void init() {
	for(int i=1; i<=n; i++) {
		for(int j=1; j<=n; j++) {
			if(i==j)graph[i][j]=paths[i][j]=0;
			else graph[i][j]=paths[i][j]=INF;
		}
	}
}

//交互式得到节点之间关系
void read() {
	int f,t,w;
	for(int i=1; i<=m; i++) {
		cin>>f>>t>>w;
		graph[f][t]=paths[f][t]=w;
		//无向图
		graph[t][f]=paths[t][f]=w;
	}
}
//Floyd算法 最短路径算法
void  floyd() {
	//核心代码
	for(int dot=1; dot<=n; dot++) {
		//以每一个点为插入点,然后更新图中任意两点以此点为中转时的路线权重
		for(int  i=1; i<=n; i++) {
			for(int j=1; j<=n; j++) {
				//经过中转后的权重是否小于原来权重
				if( paths[i][dot]+paths[dot][j] <paths[i][j]  ) {
					paths[i][j] =paths[i][dot]+paths[dot][j];
					//记录
					pair<int,int> p= {i,j};
					vector<int> v=prec[p];
					v.push_back(dot);
					prec[p]=v;
				}
			}
		}
	}
}
//找最小环
int calMinCircle() {

	int minCircle=INF;

	for(int  i=1; i<=n; i++) {
		for(int j=1; j<=n; j++) {
			pair<int,int> p= {i,j};
			vector<int> v=prec[p];
			if(v.size()==0)continue;
			//确定是不是环
			if( graph[i][j]!=INF ) {
                //找最小环
				minCircle=min(minCircle,paths[i][j]+graph[i][j] );
			}
		}
	}
	return minCircle;
}

//输出矩阵中信息
void show() {
	for(int i=1; i<=n; i++) {
		for(int j=i+1; j<=n; j++)
			cout<<paths[i][j]<<"\t";
		cout<<endl;
	}
}

int main(int argc, char** argv) {
	cin>>n>>m;
	init();
	read();
	floyd();
	//show();
	int res= calMinCircle();
	cout<<res;
	return 0;
}

测试结果:

18.png

如果是有向图,需要注意方向。如下图,2-3-5之间没有环,唯一的环是1-3-5

19.png

4.总结

本文讲解了如何使用`Floyd`算法求解次最短路径.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/221931.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【C语言】指针与数组的潜在联系

目录 前言 改变固有数组的平面思维 注意&#xff1a; 数组操作与指针等价 指针数组 数组指针 笔试加深理解&#xff1a; 解析&#xff1a; 前言 《C Traps and Pitfalls》(C语言缺陷与陷阱)中有一句著名的见解&#xff1a; “在C语言中&#xff0c;指针与数组这两个概念…

Netty核心知识总结

Netty是一个高性能、异步事件驱动的NIO框架&#xff0c;它提供了对TCP、UDP和文件传输的支持&#xff0c;作为一个异步NIO框架&#xff0c;Netty的所有IO操作都是异步非阻塞的&#xff0c;通过Future-Listener机制&#xff0c;用户可以方便的主动获取或者通过通知机制获得IO操作…

ElasticSearch篇---第三篇

系列文章目录 文章目录 系列文章目录前言一、了解ElasticSearch 深翻页的问题及解决吗?二、熟悉ElasticSearch 性能优化三、ElasticSearch 查询优化手段有哪些?前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这…

Linux 和 macOS 的主要区别在哪几个方面呢?

(꒪ꇴ꒪ )&#xff0c;Hello我是祐言QAQ我的博客主页&#xff1a;C/C语言&#xff0c;数据结构&#xff0c;Linux基础&#xff0c;ARM开发板&#xff0c;网络编程等领域UP&#x1f30d;快上&#x1f698;&#xff0c;一起学习&#xff0c;让我们成为一个强大的攻城狮&#xff0…

【信息安全】-个人敏感信息、个人信息、个人金融信息

文章目录 个人敏感信息个人敏感信息判定举例 个人信息个人信息判定举例 个人金融信息内容a) 账户信息指账户及账户相关信息b) 鉴别信息c) 金融交易信息d) 个人身份信息e) 财产信息f) 借贷信息g) 其他信息: 出处 个人敏感信息 个人敏感信息判定 个人敏感信息是指一旦泄露、非法…

ppt转换成pdf文件

最近用到了&#xff0c;记一下&#xff1b; ppt转pdf分为两种情况: 小于2007版本的 .ppt格式&#xff08;2003&#xff09; 与大于2007版本的 .pptx格式&#xff08;2007&#xff09; .ppt格式为 二进制文件 .pptx格式为xml格式&#xff0c;在java中有不同的jar包需要使用 引入…

MyBatis 常见面试题

目录 1.MyBatis——概述1.1.什么是 ORM 框架&#xff1f;1.2.✨谈谈对 MyBatis 的理解。1.3.使用 MyBatis 相对于直接使用 SQL 有哪些优点&#xff1f;1.4.MyBatis 有什么优缺点&#xff1f;1.5.✨MyBatis 的分层结构是什么样的&#xff1f;1.6.✨MyBatis 的执行流程是什么样的…

这个柴油发电机大招,再不知道就晚了!

随着能源需求的不断增长和环境问题的日益凸显&#xff0c;柴油发电机在各个行业中扮演着关键的角色&#xff0c;为企业和社会提供可靠的电力支持。 然而&#xff0c;为了确保发电机的高效运行和延长其使用寿命&#xff0c;监控和维护变得至关重要。 客户案例 制造业 某制造业…

Java 控制台命令导入本地jar包到maven本地库中

1、新建POM文件&#xff0c;在maven库路径下创建POM文件 注意&#xff1a;这个路径需要与第2点导入命令中的grouoId、artifactId和version写法对应 Path&#xff1a;D:\RomanData\repository\com\sae\mail\1.0.0\mail-1.0.0.pom <?xml version"1.0" encoding&q…

电脑屏幕亮度怎么调?学会4个方法,轻松调节亮度!

“我总是感觉我电脑屏幕太暗了&#xff0c;有时候如果光线好一点&#xff0c;会看不清电脑屏幕。有什么可以把电脑调亮一点的简单方法吗&#xff1f;” 在我们的日常生活中&#xff0c;电脑已经成为我们工作、学习、娱乐不可或缺的工具。然而&#xff0c;长时间面对电脑屏幕可能…

删除误提交的 git commit

背景描述 某次的意外 commit 中误将密码写到代码中并且 push 到了 remote repo 里面, 本文将围绕这个场景讨论如何弥补. 模拟误提交操作 在 Gitee 创建一个新的 Repo, clone 到本地 git clone https://gitee.com/lpwm/myrepo.git创建两个文件, commit 后 push 到 remote 作…

2022年第十一届数学建模国际赛小美赛B题序列的遗传过程解题全过程文档及程序

2022年第十一届数学建模国际赛小美赛 B题 序列的遗传过程 原题再现&#xff1a; 序列同源性是指DNA、RNA或蛋白质序列之间的生物同源性&#xff0c;根据生命进化史中的共同祖先定义[1]。DNA、RNA或蛋白质之间的同源性通常根据它们的核苷酸或氨基酸序列相似性来推断。显著的相…

【EI征稿中|SPIE出版】 第四届传感器与信息技术国际学术会议(ICSI 2024)

第四届传感器与信息技术国际学术会议&#xff08;ICSI 2024&#xff09; 2024 4th International Conference on Sensors and Information Technology&#xff08;ICSI 2024&#xff09; 第四届传感器与信息技术国际学术会议&#xff08;ICSI 2024&#xff09;将于2024年1月5…

VS2019shi用动态链接库

.dll文件路径包含 C/C常规-------->附加包含目录 将动态库项目文件路径包含在附加包含目录。 .lib文件路径包含 连接器------------>输入------------------>附加依赖项 .lib文件名字输出附加依赖项 连接器------------>常规------------------>附加库目录 添加…

软著项目推荐 深度学习的水果识别 opencv python

文章目录 0 前言2 开发简介3 识别原理3.1 传统图像识别原理3.2 深度学习水果识别 4 数据集5 部分关键代码5.1 处理训练集的数据结构5.2 模型网络结构5.3 训练模型 6 识别效果7 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 深度学习…

外包干了8个月,技术退步明显.......

先说一下自己的情况&#xff0c;大专生&#xff0c;18年通过校招进入武汉某软件公司&#xff0c;干了接近4年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落! 而我已经在一个企业干了四年的功能测…

开源代驾上门预约小程序源码系统:预约代驾,出行更无忧 附带完整的搭建教程

随着人们生活水平的提高&#xff0c;越来越多的人选择代驾服务。为了方便用户预约代驾&#xff0c;罗峰来给大家分享一款开源代驾上门预约系统。该系统具有以下特色功能&#xff0c;让出行更加无忧。 以下是部分代码示例&#xff1a; 系统特色功能一览&#xff1a;​​​​​​…

【计算机网络笔记】物理层——信道与信道容量

系列文章目录 什么是计算机网络&#xff1f; 什么是网络协议&#xff1f; 计算机网络的结构 数据交换之电路交换 数据交换之报文交换和分组交换 分组交换 vs 电路交换 计算机网络性能&#xff08;1&#xff09;——速率、带宽、延迟 计算机网络性能&#xff08;2&#xff09;…

理解依赖注入

1 回顾Spring IoC容器 1.1 Spring IoC容器 Web应用程序由大量的程序组件组成&#xff0c;这些组件一起工作完成业务逻辑的执行。 这些组件通常是一个依赖另一个&#xff0c;互相协作实现所需功能。 Spring提供容器&#xff0c;也就是IoC容器&#xff0c;来管理这些组件&…

万界星空科技MES系统在工业生产中的应用

万界星空科技MES系统在工业生产中的应用广泛。它适用于各类制造业&#xff0c;包括汽车制造、电子制造、注塑、能源化工、航天航空、食品加工、服装纺织、灯具、电线电缆、电机发动机、印刷包装等行业。 在汽车制造领域&#xff0c;MES系统可以实时追踪和控制整个生产过程&…