【数据结构】图——最短路径

news2025/4/9 15:43:41

最短路径问题:从在带权有向图G中的某一顶点出发,找出一条通往另一顶点的最短路径,最短也就是沿路径各边的权值总和达到最小。

最短路径分为图中单源路径和多源路径。

本文会介绍Dijkstra和Bellman-Ford解决单源路径的问题

Floyd-Warshall解决多路径的问题

单源最短路径--Dijkstra算法

单源问题,对于图中给定的结点u,求出到达各个结点v的路径代价和最小。最小路径可能是直接u->v

也可能是从u->t->v  。

Dijkstra算法就是解决所有边非负权值的最短路径。一般是从给出点u到终点v.

算法的思路

将图中的所有结点可以分为S和Q,S是已经确定最短路径的集合,Q是还没被访问的集合)(初始化时,将s放到集合中)

每一次从Q中选出一个s到Q代价最小的点u,把u从s移出放到S中,对u的相邻v 进行松弛更新。

松弛更新:判断s->u(已经确定了)  u->v的路径是否小于s->v(已经确定的)

如果是的话,就更新s->v

直到更新所有的Q结点

在我们的模拟实现中

保存一张dist[]数组,用来表示s到某点的最短路径

S[]数组用来表示已经确定最短路径的顶点、

pPath用来存储路径前一个顶点的下标

通过画图梳理这个过程

首次初始化时,dist数组s位置是0,其它为无穷,pPath数组s位置是缺省值

第二轮:寻找最短路径s-s 松弛更新出s->t s->y

第三步 选出最小v  s->y(s->s已经被标记过)

松弛更新出s-y-t s-y-x s-y-z

第三轮选边 选出s->z 对z相邻的边松弛更新

s->z->x(13)

s->z->s(14 不更新)

第四轮

选边s->t

第五轮 选出s->x 松弛更新出 x->z(不更新,已经是最短路径)

松弛更新不适合用于带负权值的路径

因为无法确定第一轮的最小值

		void Dijkstra(const V& src, vector<W>& dist, vector<int>& pPath)
		{
			size_t srci = GetVertexIndex(src);
			size_t n = _vertexs.size();
			dist.resize(n, MAX_W);
			pPath.resize(n, -1);

			//自身就是0
			dist[srci] = W();
			pPath[srci] = srci;

			//标记容器
			vector<bool> S(n, false);

			for (size_t j = 0; j < n; j++)
			{
				int u = 0;
				W min = MAX_W;
				for (size_t i = 0; i < n; i++)
				{
					if (S[i] == false && dist[i] < min)
					{
						u = i;
						min = dist[i];
					}
				}

				S[u] = true;
				//松弛更新
				for (size_t v = 0; v < n; v++)
				{
					if (S[v] == false && _matrix[u][v] != MAX_W &&
						dist[u] + _matrix[u][v] < dist[v])
					{
						dist[v] = dist[u] + _matrix[u][v];
						pPath[v] = u;
					}
				}
			}
		}

单源最短路径--Bellman-Ford算法
 

迪杰斯特拉算法不能解决带负权值边的最短路径,贝尔曼福特就采用暴力的方式解决了带负权值边的路径问题,还能判断出回路是否带有负权值。

贝尔曼福特算法的思路是将 srci-> j的路径问题划分为srci-> i  i->j的路径最短问题

对于从源顶点 s  到目标顶点 j 的路径来说,如果存在从源顶点 s 到顶点 i 的路径,还存在一条从顶点 i  到顶点 j  的边,并且其权值之和小于当前从源顶点 s  到目标顶点 j 的路径长度,则可以对顶点 j j 的估计值和前驱顶点进行松弛更新。

是利用已经确定的最短路径srci -i 再加上i->j的边权来更新的最短路径。

确定完所有的顶点后,都必须重新进行更新,因为每一条路径的确定,都有可能影响前一轮的路径更新。故需要遍历K次。K为N次。(因为所有的点都在变,都需要更新。)

初始化时,dist[srci]初始化为缺省值

第一次i==0时,更新出t y

第二次,i==1更新出x z 

i==2时,更新出 x s(不更新,目前是最短路径)

i==3时,更新出t->z

i==4时,用x更新出t

结束第一轮

此时还需要进行下一轮的更新

下一轮再i==3时,更新出t->z=-4

		bool BellmanFord(const V& src, vector<W>& dist, vector<int>& pPath)
		{
			size_t n = _vertexs.size();
			size_t srci = GetVertexIndex(src);

			dist.resize(n, MAX_W);
			pPath.resize(n, -1);
			
			dist[srci] = W();

			//最多更新k轮
			for (size_t k = 0; k < n; k++)
			{
				bool updata = false;
				cout << "更新第:" << k << "轮" << endl;
				//srci->i+i->j
				for (size_t i = 0; i < n; i++)
				{
					for (size_t j = 0; j < n; j++)
					{
						if (_matrix[i][j] != MAX_W && dist[i] + _matrix[i][j] < dist[j])
						{
							updata = true;
							cout << _vertexs[i] << "->" << _vertexs[j] << ":" << _matrix[i][j] << endl;
							dist[j] = dist[i] + _matrix[i][j];
							pPath[j] = i;

						}
					}
				}
				if (!updata)
					break;
			}
			//如果还能更新,就是带负路径
			for (size_t i = 0; i < n; i++)
			{
				for (size_t j = 0; j < n; j++)
				{
					if (_matrix[i][j] != MAX_W && dist[i] + _matrix[i][j] < dist[j])
					{
						return false;
					}
				}
			}
			return true;
		}

利用程序编写过程和我们的画图一致

贝尔曼福特算法是暴力求解,有相当高的时间复杂度O(N^3),但是能够解决负权值问题。


多源最短路径--Floyd-Warshall算法

Floyd-Warshall算法是解决任意两点间的最短路径的一种算法。

弗洛伊德算法考虑的是中间最短路径的中间结点,设k是p的一个中间节点,那么从i到j的最短路径p就被分成i到k和k到j的两段最短路径p1,p2。p1是从i到k且中间节点属于{1,2,…,k-1}取得的一条最短路径。p2是从k到j且中间节点属于{1,2,…,k-1}取得的一条最短路径。

Floyd-Warshall算法是一种动态规划算法,其运行时间为o(n^3)与最短路径路径上通常的假设一样,假设权重可以为负,但不能有权重为负的环路

如果存在从顶点 i  到顶点 k  的路径,还存在从顶点 k  到顶点 j  的路径,并且这两条路径的权值之和小于当前从顶点 i 到顶点 j 的路径长度,则可以对顶点 j  的估计值和前驱顶点进行松弛更新。

算法原理:

费洛伊徳算法就是将最短路径分成dist[i][k]+ dist[k][j]与dist[k][j]的问题

算法需要一个二维的vvdist记录最短路径,vvpPath标记父路径

将直接相连的视作最短路径 

进行k次更新(K为n,i和j都在变换)

动态规划更新vvdist

注意vvpPath[i][j]的前一个不再是i而应该是递归的vvpPath[k][j],可能变化一次,也可能变化好多次

		void FloydWarshall(vector<vector<W>>& vvDist, vector<vector<int>>& vvpPath)
		{
			size_t n = _vertexs.size();
			vvDist.resize(n, vector<W>(n, MAX_W));
			vvpPath.resize(n, vector<int>(n, -1));

			//更新权值和父路径
			for (size_t i = 0; i < n; i++)
			{
				for (size_t j = 0; j < n; j++)
				{
					if (_matrix[i][j] != MAX_W)
					{
						vvDist[i][j] = _matrix[i][j];
						vvpPath[i][j] = i;
					}

					if (i == j)
					{
						vvDist[i][j] = W();
					}
				}
			}

			//O(N^3)
			//最多更新k次
			for (size_t k = 0; k < n; k++)
			{
				for (size_t i = 0; i < n; i++)
				{
					for (size_t j = 0; j < n; j++)
					{
						//存在 i->k  k->j 且<dist[i][j]
						if (vvDist[i][k] != MAX_W && vvDist[k][j] != MAX_W
							&& vvDist[i][k] + vvDist[k][j] < vvDist[i][j])
						{
							vvDist[i][j] = vvDist[i][k] + vvDist[k][j];
							vvpPath[i][j] = vvpPath[k][j];
						}
					}
				}
				// 打印权值和路径矩阵观察数据
				for (size_t i = 0; i < n; ++i)
				{
					for (size_t j = 0; j < n; ++j)
					{
						if (vvDist[i][j] == MAX_W)
						{
							//cout << "*" << " ";
							printf("%3c", '*');
						}
						else
						{
							//cout << vvDist[i][j] << " ";
							printf("%3d", vvDist[i][j]);
						}
					}
					cout << endl;
				}
				cout << endl;

				for (size_t i = 0; i < n; ++i)
				{
					for (size_t j = 0; j < n; ++j)
					{
						//cout << vvParentPath[i][j] << " ";
						printf("%3d", vvpPath[i][j]);
					}
					cout << endl;
				}
				cout << "=================================" << endl;
			}

		}

最短路径的总结

迪杰斯特拉算法是基于贪心算法设计,解决非负数权值的路径问题。将顶点分为S和Q,每次都从Q中选出最小的权值u放到s中,然后将u的相邻边进行松弛调整。

贝尔曼福特算法是暴力求解,能求解出带负权值的路径,能判断负权环路。是一个时间复杂度为o(N^3)的算法。要进行K次松弛调整。每一次都是通过dist[i]+_matrix[i][j]更新权值

费洛伊徳算法是动态规划的思想,将路径分为i->k k->j的思想。同样要进行K轮次的更新。复杂度为0(N^3)。能够求解任意俩顶点的最短路径。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SCI一区 | Matlab实现ST-CNN-MATT基于S变换时频图和卷积网络融合多头自注意力机制的多特征分类预测

SCI一区 | Matlab实现ST-CNN-MATT基于S变换时频图和卷积网络融合多头自注意力机制的故障多特征分类预测 目录 SCI一区 | Matlab实现ST-CNN-MATT基于S变换时频图和卷积网络融合多头自注意力机制的故障多特征分类预测效果一览基本介绍模型描述程序设计参考资料 效果一览 基本介绍…

【牛客】2024牛客寒假算法基础集训营6ABCDEGHIJ

文章目录 A 宇宙的终结题目大意主要思路代码 B 爱恨的纠葛题目大意主要思路代码 C 心绪的解剖题目大意主要思路代码 D 友谊的套路题目大意主要思路代码 E 未来的预言题目大意主要思路代码 G 人生的起落题目大意主要思路代码 I 时空的交织题目大意主要思路代码 J 绝妙的平衡题目…

ChatGPT调教指南 | 咒语指南 | Prompts提示词教程(三)

在人工智能成为我们日常互动中无处不在的一部分的时代&#xff0c;与大型语言模型(llm)有效沟通的能力是无价的。“良好提示的26条原则”为优化与这些复杂系统的交互提供了全面的指导。本指南证明了人类和人工智能之间的微妙关系&#xff0c;强调清晰、专一和结构化的沟通方法。…

leetcode hot100 买卖股票最佳时机3

本题中&#xff0c;依旧可以采用动态规划来进行解决&#xff0c;之前的两个题我们都是用二维数组dp[i][2]来表示的&#xff0c;其中i表示第i天&#xff0c;2表示长度为2&#xff0c;其中0表示不持有&#xff0c;1表示持有。 本题中&#xff0c;说至多完成两笔交易&#xff0c;也…

RabbitMQ 面试八股题整理

前言&#xff1a;本文是博主网络自行收集的一些RabbitMQ相关八股文&#xff0c;还在准备暑期实习&#xff0c;后续应该会持续更新...... 参考&#xff1a;三天吃透RabbitMQ面试八股文_牛客网 目录 RabbitMQ概述 什么是 RabbitMQ&#xff1f; 说一说RabbitMQ中的AMQP 为什么…

单机取证-信息安全管理与评估-2022年国赛真题-环境+wp

🍬 博主介绍 博主介绍:大家好,我是 Mikey ,很高兴认识大家~ 主攻:【应急响应】 【python】 【数字取证】【单机取证】【流量分析】【MISC】 🎉点赞➕评论➕收藏 == 养成习惯(一键三连)😋 🎉欢迎关注💗一起学习👍一起讨论⭐️一起进步 作者水平有限,欢迎各…

网络层的DDoS攻击与应用层的DDoS攻击之间的区别

DDoS攻击&#xff08;即“分布是拒绝服务攻击”&#xff09;&#xff0c;是基于DoS的特殊形式的拒绝服务攻击&#xff0c;是一种分布式、协作的大规模攻击方式&#xff0c;主要瞄准一些企业或政府部门的网站发起攻击。根据攻击原理和方式的区别&#xff0c;可以把DDoS攻击分为两…

(done) 如何判断一个矩阵是否可逆?

参考视频&#xff1a;https://www.bilibili.com/video/BV15H4y1y737/?spm_id_from333.337.search-card.all.click&vd_source7a1a0bc74158c6993c7355c5490fc600 这个视频里还暗含了一些引理 1.若 AX XB 且 X 和 A,B 同阶可逆&#xff0c;那么 A 和 B 相似。原因&#xff1…

RDMA内核态函数ib_post_recv()源码分析

接上文&#xff0c;上文分析了内核rdma向发送队列添加发送请求的函数ib_post_send&#xff0c;本文分析一下向接收队列添加接收请求的函数ib_post_recv。其实函数调用流程与上文类似&#xff0c;不再重复说明&#xff0c;可参考链接。 函数调用过程 最终会调用到这个函数 下面…

Stable Diffusion 绘画入门教程(webui)-ControlNet(Inpaint)

上篇文章介绍了语义分割Tile/Blur&#xff0c;这篇文章介绍下Inpaint&#xff08;重绘&#xff09; Inpaint类似于图生图的局部重绘&#xff0c;但是Inpain效果要更好一点&#xff0c;和原图融合会更加融洽&#xff0c;下面是案例&#xff0c;可以看下效果&#xff08;左侧原图…

【Java多线程】对线程池的理解并模拟实现线程池

目录 1、池 1.1、线程池 2、ThreadPoolExecutor 线程池类 3、Executors 工厂类 4、模拟实现线程池 1、池 “池”这个概念见到非常多&#xff0c;例如常量池、数据库连接池、线程池、进程池、内存池。 所谓“池”的概念就是&#xff1a;&#xff08;提高效率&#xff09; 1…

pytorch -- ToTensor使用

1. ToTensor定义 导入&#xff1a;from torchvision import transforms 通过transforms.ToTensor解决两个问题&#xff08;PIL image/numpy.ndarray 转化为 tensor &#xff09; ToTensor()返回一个ToTensor的对象(创建具体的工具)&#xff0c;传入pic就会返回一个Tensor类型的…

应急响应实战笔记03权限维持篇(4)

第4篇&#xff1a;Linux权限维持--后门篇 本文将对Linux下常见的权限维持技术进行解析&#xff0c;知己知彼百战不殆。 1、一句话添加用户和密码 添加普通用户&#xff1a; # 创建一个用户名guest&#xff0c;密码123456的普通用户 useradd -p openssl passwd -1 -salt sal…

Spring的另一大的特征:AOP

目录 AOP &#xff08;Aspect Oriented Programming&#xff09;AOP 入门案例&#xff08;注解版&#xff09;AOP 工作流程——代理AOP切入点表达式AOP 通知类型AOP通知获取数据获取切入点方法的参数获取切入点方法返回值获取切入点方法运行异常信息 百度网盘分享链接输入密码数…

[数据集][目标检测]游泳者溺水数据集VOC+YOLO格式2类别895张

数据集制作单位&#xff1a;未来自主研究中心(FIRC) 数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;895 标注数量(xml文件个数)&#xff1a…

第7.1章:StarRocks性能调优——查询分析

目录 一、查看查询计划 1.1 概述 1.2 查询计划树 1.3 查看查询计划的命令 1.3 查看查询计划 二、查看查询Profile 2.1 启用 Query Profile 2.2 获取 Query Profile 2.3 Query Profile结构与详细指标 2.3.1 Query Profile的结构 2.3.2 Query Profile的合并策略 2.…

计算机视觉基础知识(十五)--卷积神经网络

卷积神经网络简介 CNN--卷积神经网络&#xff0c;是一种前馈神经网络&#xff1b;不同于传统的只有线性连接的神经网络&#xff1b;CNN具有卷积&#xff08;convolution&#xff09;操作、池化&#xff08;pooling&#xff09;和非线性激活函数映射等&#xff1b;经典CNN网络有…

【Linux】--- 详解Linux软件包管理器yum和编辑器vim

目录 一、Linux软件包管理器 - yum1.1 yum和软件包是什么1.2 Linux系统(Centos)的生态1.3 yum相关操作1.4 yum本地配置 二、Linux编辑器 - vim使用2.1 vim的基本概念2.2 vim命令模式命令集2.3 vim末行模式命令集2.4 关于vim的几个相关问题 一、Linux软件包管理器 - yum 1.1 yu…

Codeforce Monsters Attack!(B题 前缀和)

题目描述&#xff1a; 思路&#xff1a; 本人第一次的想法是先杀血量低的第二次想法是先搞坐标近的第三次想法看到数据量这么大&#xff0c; 我先加个和看看貌似我先打谁都行&#xff0c;由此综合一下&#xff0c; 我们可以把每一个不同的坐标当作一轮从最小的坐标开始&#x…

Vue+SpringBoot打造在线课程教学系统

目录 一、摘要1.1 系统介绍1.2 项目录屏 二、研究内容2.1 课程类型管理模块2.2 课程管理模块2.3 课时管理模块2.4 课程交互模块2.5 系统基础模块 三、系统设计3.1 用例设计3.2 数据库设计 四、系统展示4.1 管理后台4.2 用户网页 五、样例代码5.1 新增课程类型5.2 网站登录5.3 课…