图论必备:Dijkstra、Floyd与Bellman-Ford算法在最短路径问题中的应用

news2024/11/15 8:45:45

 

                                               🎬慕斯主页修仙—别有洞天

                                              ♈️今日夜电波:アンビバレント—Uru

                                                                0:24━━━━━━️💟──────── 4:02
                                                                    🔄   ◀️   ⏸   ▶️    ☰  

                                      💗关注👍点赞🙌收藏您的每一次鼓励都是对我莫大的支持😍


 

什么是最短路径?

        最短路径问题:从在带权有向图G中的某一顶点出发,找出一条通往另一顶点的最短路径,最短也就是沿路径各边的权值总和达到最小 即路径上各边权值之和最小的路径。以下是关于最短路径问题的一些关键信息:

  • Dijkstra算法:这是一种广泛使用的算法,用于在非负权重的图中找到一个顶点到其他所有顶点的最短路径。它使用贪心策略,每次从未访问的顶点中选择一个距离起点最近的顶点,并更新其他顶点到起点的距离。需要注意的是,Dijkstra算法不能处理带有负权重边的图。
  • Bellman-Ford算法:这是另一种算法,它可以处理带有负权重边的图。该算法通过对所有的边进行V-1次松弛操作来找到最短路径,其中V是图中顶点的数量。如果图中存在负权重环,则算法会报告这一情况。
  • Floyd-Warshall算法:这是一种动态规划算法,它可以计算图中任意两点之间的最短路径。该算法的时间复杂度较高,为O(V^3),但它可以处理包含负权重边的图,只要没有负权重环。
  • 最短路径的性质:最短路径具有最优子结构的性质,即从起点到任一点的最短路径上的任何子路径也是最短的。这一性质是Dijkstra算法和大多数最短路径算法的基础。

Dijkstra算法

如何理解Dijkstra算法?

        Dijkstra算法(迪杰斯特拉算法)是一种非常著名的单源最短路径算法,用于计算一个节点到其他所有节点的最短路径。这个算法采用了贪心策略,每次迭代中都会选择当前未处理节点中距离起始点最近的节点,然后更新该节点的所有邻居节点的距离值。

        以下是Dijkstra算法的基本步骤:

  1. 初始化:将起始节点的距离设为0,其他所有节点的距离设为无穷大(表示当前还无法到达这些节点)。同时,创建一个已处理节点集合和一个未处理节点集合,起始节点放入已处理节点集合,其他节点放入未处理节点集合。
  2. 选择节点:从未处理的节点集合中选择一个距离起始点最近的节点,将其加入已处理节点集合。
  3. 更新邻居:对于刚刚选出的节点的每一个邻居节点,如果通过当前节点到达该邻居节点的距离比之前记录的距离更短,则更新该邻居节点的距离值。
  4. 重复:重复步骤2和步骤3,直到所有节点都已经被处理。

        在算法的实现过程中,通常会使用优先队列(例如最小堆)来优化选择节点的步骤,使得每次都能快速找到未处理节点中距离起始点最近的节点。

        需要注意的是,Dijkstra算法不能处理带有负权重的边的情况。如果图中存在负权重的边,那么算法可能无法正确计算出最短路径。此外,虽然Dijkstra算法可以处理带有权重的图,但它不能处理存在环的情况,特别是当环的权重之和为负时

        总的来说,Dijkstra算法是一种非常有效的单源最短路径算法,其思想直观且易于理解,是图论和计算机科学中的基础知识之一。

Dijkstra算法的实现

        Dijkstra算法还是基于邻接矩阵上实现的。对于函数的接口,我们是通过给定的顶点来确定从谁开始到其他顶点的最短路径的,还需要额外传递两个参数dist存储到各顶点的最短路径,pPath存储他们的父亲节点。接下来我们一步一步的实现:(1)初始化,获得对应顶点的下标,初始化dist和pPath,需要注意其中源顶点到源顶点的值即为0,源顶点的父亲即为源顶点,再定义一个已经确定最短路径的顶点S集合用于后续的操作。(2)开始遍历,我们需要找到n个顶点的最小路径,所以需要进行n次循环。(3)每次循环都需要定义两个变量,u用于存储S中没确定最短路径的顶点,min则用于选出现在顶点到u最短的边。(4)u被选出来,说明u是相邻最小的边的顶点了,因此我们可以将S中u的位置至为true,表示已径确定最小路径的顶点。(5)后续在u被选出来后,我们需要根据u来更新源顶点通过u到另外顶点的最短距离,松弛更新u连接顶点v。(6)同样是一个循环,遍历u->v中的v这个顶点,我们可以通过_matrix[u][v] != MAX_W来判断u是否可以到v来筛选v,再通过S[v] == false来确定v并没有被选出最短路径,最后根据 dist[u] + _matrix[u][v] < dist[v] 来判断是否松弛更新u连接顶点v。(7)最后符合条件则更新v在dist中的值,以及更新v的父节点u在pPath中。具体实现如下:

		void Dijkstra(const V& src, vector<W>& dist, vector<int>& pPath)//dist存储到各顶点的最短路径,pPath存储他们的父亲节点。
		{
			size_t srci = GetVertexIndex(src);//获取对应顶点的下标映射
			size_t n = _vertexs.size();
			dist.resize(n, MAX_W);
			pPath.resize(n, -1);

			dist[srci] = 0;//源顶点到源顶点的值即为0
			pPath[srci] = srci;//源顶点的父亲即为源顶点

			// 已经确定最短路径的顶点集合
			vector<bool> S(n, false);

			for (size_t j = 0; j < n; ++j)
			{
				// 选最短路径顶点且不在S更新其他路径
				int u = 0;
				W min = MAX_W;
				for (size_t i = 0; i < n; ++i)
				{
					if (S[i] == false && dist[i] < min)//S[i]还没有被选过且找到最小的边
					{
						u = i;//u是不在S中的点
						min = dist[i];
					}
				}

				//u被选出来,说明u是相邻最小的边的顶点了
				S[u] = true;
				// 松弛更新u连接顶点v  srci->u + u->v <  srci->v  更新
				for (size_t v = 0; v < n; ++v)
				{
					if (S[v] == false && _matrix[u][v] != MAX_W
						&& dist[u] + _matrix[u][v] < dist[v])//v表示u连接出去边的顶点
					{
						dist[v] = dist[u] + _matrix[u][v];
						pPath[v] = u;
					}
				}
			}
		}

Bellman-Ford算法

如何理解Bellman-Ford算法?

        Bellman-Ford算法是一种用于解决单源最短路径问题的算法,特别是在图中包含负权边的情况下

        Bellman-Ford算法的核心思想是“松弛操作”,通过不断迭代来更新最短路径的估计值。这个算法可以处理存在负权边的图,解决了Dijkstra算法无法处理负权边的问题。Bellman-Ford算法通过m次迭代来确定从源点到终点不超过m条边构成的最短路径。在没有负环的情况下,这个算法能够找到正确的最短路径。然而,如果图中存在负环,算法也能检测到这一点。

        具体来说,Bellman-Ford算法的工作原理如下:

  1. 初始化:将所有节点的最短路径估计值初始化为无穷大,除了源点自身的估计值为0。
  2. 迭代:对于图中的每一条边,尝试通过该边进行松弛操作,即如果通过这条边能够得到一个更小的最短路径估计值,就更新这个估计值。需要注意的是:如果我们在更新完一个顶点的边后,如果通过该顶点的边可以让前面的边更小,但是我们的更新已经完成了,那么这样就出错了!因此,可以通过让整个已经完整迭代过的最小生成树再次进行迭代(迭代多少次呢?如果每次迭代都出现上述的情况,那么最多就是n次!)因此他的时间复杂度是比较高的:O(N^3)
  3. 检测负环:如果在迭代完成后,再次进行迭代仍然可以进行松弛操作,那么图中必然存在负环。

Bellman-Ford算法的实现

        Bellman-Ford算法也是基于邻接矩阵的基础上实现的。还是通过传递一个源顶点来确定从谁开始到其他顶点的最短路径的,还需要额外传递两个参数dist存储到各顶点的最短路径,pPath存储他们的父亲节点。接下来我们一步一步的实现:(1)初始化,更新dist 记录srci-其他顶点最短路径权值数组、pPath 记录srci-其他顶点最短路径父顶点数组,最后更新dist中传入的源顶点的位置未缺省值(实际上就是0)。(2)按照上述的原理,我们要对图中的每一条边都尝试通过该边进行松弛操作,这就体现到邻接矩阵的优势所在了。只需要两个for循环即可遍历所有的边。我们在之前的文章对邻接矩阵中不能连接到的边做了将他置为MAX_W的处理,因此根据该条件即可知道是否可以由i直接连接到j需要注意的是:如果说Dijkstra贪心的是第一条边,那么Bellman-Ford算法的思想实际上是贪心的最后的一条边。我们只需要根据是否可以用更小的权值到达目标边即可。(为什么可以这么做?因目标顶点的最近顶点的dist非MAX_W就说明了从原顶点一定是可以到达最近顶点的,而现在你现在合起来的权值有比原来的小,这就是说明是一种更好的路径!)如下写出初步代码:

	for (size_t i = 0; i < n; ++i)
	{
		for (size_t j = 0; j < n; ++j)
		{
			// srci -> i + i ->j
			if (_matrix[i][j] != MAX_W && dist[i] + _matrix[i][j] < dist[j])
			{
				dist[j] = dist[i] + _matrix[i][j];
				pPath[j] = i;
			}
		}
	}

(3)为什么说是初步代码呢?这是因为上面原理提到的后续的更新完了,后面的更新可以使前面的路径更小!因此我们需要让他们再次更新!(最多跟新n次!即每次更新都会造成新的更短的路径!)如果更新了n次后还存在更新操作,那么就是带负权的回路!具体实现如下:

		//  空间复杂度:O(N)
		bool BellmanFord(const V& src, vector<W>& dist, vector<int>& pPath)
		{
			size_t n = _vertexs.size();
			size_t srci = GetVertexIndex(src);

			// vector<W> dist,记录srci-其他顶点最短路径权值数组
			dist.resize(n, MAX_W);

			// vector<int> pPath 记录srci-其他顶点最短路径父顶点数组
			pPath.resize(n, -1);

			// 先更新srci->srci为缺省值
			dist[srci] = W();

			// 总体最多更新n轮
			for (size_t k = 0; k < n; ++k)
			{
				// i->j 更新松弛
				bool update = false;
				for (size_t i = 0; i < n; ++i)
				{
					for (size_t j = 0; j < n; ++j)
					{
						// srci -> i + i ->j
						if (_matrix[i][j] != MAX_W && dist[i] + _matrix[i][j] < dist[j])
						{
							update = true;
							dist[j] = dist[i] + _matrix[i][j];
							pPath[j] = i;
						}
					}
				}

				// 如果这个轮次中没有更新出更短路径,那么后续轮次就不需要再走了
				if (update == false)
				{
					break;
				}
			}


			// 还能更新就是带负权回路
			for (size_t i = 0; i < n; ++i)
			{
				for (size_t j = 0; j < n; ++j)
				{
					// srci -> i + i ->j
					if (_matrix[i][j] != MAX_W && dist[i] + _matrix[i][j] < dist[j])
					{
						return false;
					}
				}
			}

			return true;
		}

FloydWarshall算法

如何理解FloydWarshall?

        Floyd-Warshall算法是一种计算图中所有顶点对之间最短路径的动态规划算法

        该算法的核心思想是逐步更新两个顶点之间的最短路径,直至包含所有其他顶点作为中间顶点为止。这样,通过不断添加中间顶点来更新最短路径,最终得到的结果就是所有顶点对之间的最短路径。

        具体来说,Floyd-Warshall算法的工作原理如下:

  1. 初始化:将图的邻接矩阵作为初始距离矩阵,如果两个顶点之间没有直接相连的边,则距离设为无穷大。
  2. 迭代更新:使用动态规划的思想,逐步考虑所有可能的中间顶点。对于每一对顶点(u, v),检查是否存在一个中间顶点w,使得从u到w再到v的距离比当前记录的u到v的距离更短。如果是,则更新u到v的最短距离。
  3. 结果:经过n次迭代后,其中n为图中顶点的数量,得到的矩阵即为所有顶点对之间的最短路径。

        总的来说,Floyd-Warshall算法在解决无向图中的最短路径问题时非常有效,特别是当需要找到所有顶点对之间的最短路径时。然而,它的时间复杂度较高,为O(n^3),因此在处理大型图时可能不是最优选择。此外,该算法不能处理带有负权重边的图,因为可能存在负权重环导致的无限递减问题。

FloydWarshall算法的实现

        FloydWarshall算法也是基于邻接矩阵的基础上实现的。可以看到三大最短路径以及最小生成树的算法都通过邻接矩阵来实现的,这也说明了邻接矩阵的高效,当然有些算法也是可以使用的邻接表实现的。FloydWarshall算法传入两个二维的矩阵vvDist和vvpPath,分别用于存储权值以及路径,这很明显的就是一个二维dp的运用。接下来我们一步一步的实现:(1)初始化,初始化vvDist距离设为无穷大和vvpPath路径设为-1,接着更新直接相连的边。(2)本算法的状态表示为 D[i][j][k]表示从点i到点j只经过0到k个点最短路径 。以及状态转移方程为:

(3)按照上述的原理我们以最短路径的更新i-> {其他顶点} ->j。在这里,我们让第一层循环k作为的中间点尝试去更新i->j的路径(因为所有点都可以成为中间点包括他自己、目标点),需要注意的是:再次强调本算法是将更新所有顶点到所有顶点的最短路径!因此我们需要两层循环:一层i表示源顶点,一层j表示目标顶点。(4)由于上面初始化的时候已经将不经过k的情况都初始化完成了,那么我们直接判断经过k的时候是否比不经过k小即可,但是需要注意前提是:i到k和k到j是存在对应的边的。而父路径下标的存储的是j上一个顶点,这个顶点是带商讨的,因此这也是为什么我们用二维矩阵的原因,因为他也同步更新的!也就是要存vvpPath[k][j]。需要具体实现如下:

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

			// 初始化权值和路径矩阵
			for (size_t i = 0; i < n; ++i)
			{
				vvDist[i].resize(n, MAX_W);
				vvpPath[i].resize(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();
					}
				}
			}

			// abcdef  a {} f ||  b {} c
			// 最短路径的更新i-> {其他顶点} ->j
			for (size_t k = 0; k < n; ++k)
			{
				for (size_t i = 0; i < n; ++i)
				{
					for (size_t j = 0; j < n; ++j)
					{
						// k 作为的中间点尝试去更新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];

							// 找跟j相连的上一个邻接顶点
							// 如果k->j 直接相连,上一个点就k,vvpPath[k][j]存就是k
							// 如果k->j 没有直接相连,k->...->x->j,vvpPath[k][j]存就是x

							vvpPath[i][j] = vvpPath[k][j];
						}
					}
				}
			}	
		}

 


                        感谢你耐心的看到这里ღ( ´・ᴗ・` )比心,如有哪里有错误请踢一脚作者o(╥﹏╥)o! 

                                       

                                                                        给个三连再走嘛~  

 

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

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

相关文章

Javaweb学习记录(二)web开发入门(请求响应)

第一个基于springboot的web请求程序 通过创建一个带有springboot的spring项目&#xff0c;项目会自动生成一个程序启动类&#xff0c;该类启动时会启动该整个项目&#xff0c;而我们需要写一个web请求类&#xff0c;要求在本地浏览器上发送请求后&#xff0c;浏览器显示Hello&…

python --- 练习题3

目录 1、猜数字游戏&#xff08;使用random模块完成&#xff09; &#xff1a;继上期题目&#xff0c;附加 2、用户登录注册案例 3、求50~150之间的质数是那些&#xff1f; 4、打印输出标准水仙花数&#xff0c;输出这些水仙花数 5、验证:任意一个大于9的整数减去它的各位…

【数据库系统】数据库完整性和安全性

第六章 数据库完整性和安全性 基本内容 安全性&#xff1b;完整性&#xff1b;数据库恢复技术&#xff1b;SQL Server的数据恢复机制&#xff1b; 完整性 实体完整性、参照完整性、用户自定义完整性 安全性 身份验证权限控制事务日志&#xff0c;审计数据加密 数据库恢复 冗余…

中国贸易金融跨行交易区块链平台CTFU、区块链福费廷交易平台BCFT、中国人民银行贸易金融区块链平台CTFP、银行函证区块链服务平台BPBC

中国人民银行贸易金融区块链平台CTFP介绍 贸易金融的发展概况及存在的问题 1.1 贸易金融的概况 贸易金融是指商业银行在贸易双方债权债务关系的基础上&#xff0c;为国内或跨国的商品和服务贸易提供的贯穿贸易活动整个价值链、全程全面性的综合金融服务。伴随全球化的进程&a…

互联网思维:息共享、开放性、创新和快速反应、网络化、平台化、数据驱动和用户体验 人工智能思维:模拟人、解放劳动力、人工智能解决方案和服务

互联网思维&#xff1a;信息共享、开放性、创新和快速反应、网络化、平台化、数据驱动和用户体验 互联网思维是指一种以互联网为基础的思考方式&#xff0c;强调信息共享、开放性、创新和快速反应的特点。这种思维方式注重网络化、平台化、数据驱动和用户体验&#xff0c;以适…

simulink里枚举量的使用--在m文件中创建枚举量实践操作(推荐)

本文将介绍一种非常重要的概念&#xff0c;枚举量&#xff0c;以及它在simulink状态机中的使用&#xff0c;并且给出模型&#xff0c;方便大家学习。 枚举量&#xff1a;实际上是用一个名字表示了一个变量&#xff0c;能够比较方便的表示标志信息 A.简单举例&#xff1a; 1&a…

Hack The Box-Analytics

目录 信息收集 namp whatweb WEB 信息收集 feroxbuster RCE漏洞 提权 get user get root 信息收集 namp 端口信息探测┌──(root㉿ru)-[~/kali/hackthebox] └─# nmap -p- 10.10.11.233 --min-rate 10000 Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-03-…

经典双指针问题

思路;先找到第一个包含m家店的区间&#xff08;l-r&#xff09;&#xff0c;然后开始进行双指针&#xff08;l&#xff0c;r&#xff09;滑动(如下滑动) while(r<n){while(vis[a[l]]>1)//当前l-r之间a[l]店铺有多个&#xff08;大于一个&#xff09;&#xff0c;那即可去…

macOS下Java应用的打包和安装程序制作

macOS应用程序结构 macOS通常以dmg或pkg作为软件发行包&#xff0c;安装到/Applications下后&#xff0c;结构比较统一。 info.plist里的CFBundleExecutable字段可以指定入口&#xff0c;如果不指定&#xff0c;则MacOS下必须存在同名可执行文件。即abc.app下必须存在abc.app/…

从原理到实践:深入探索Linux安全机制(一)

前言 本文将从用户和权限管理、文件系统权限、SELinux、防火墙、加密和安全传输、漏洞管理和更新等几个Linux安全机制中的重要方面&#xff0c;深入探索其工作原理和使用方法。在当今数字化时代&#xff0c;网络安全问题备受关注&#xff0c;Linux作为广泛应用的操作系统之一&…

【GPT概念04】仅解码器(only decode)模型的解码策略

一、说明 在我之前的博客中&#xff0c;我们研究了关于生成式预训练转换器的整个概述&#xff0c;以及一篇关于生成式预训练转换器&#xff08;GPT&#xff09;的博客——预训练、微调和不同的用例应用。现在让我们看看所有仅解码器模型的解码策略是什么。 二、解码策略 在之前…

财报解读:“高端化”告一段落,华住开始“全球化”?

2023年旅游业快速复苏&#xff0c;全球酒店业直接受益&#xff0c;总体运营指标大放异彩&#xff0c;多数酒店企业都实现了营收上的明显增长&#xff0c;身为国内龙头的华住也不例外。 3月20日晚&#xff0c;华住集团发布2023年四季度及全年财报。整体实现扭亏为盈&#xff0c;…

阿里云安装宝塔后面板打不开

前言 按理来说装个宝塔面板应该很轻松的&#xff0c;我却装了2天&#xff0c;真挺恼火的&#xff0c;网上搜的教程基本上解决不掉我的问题点&#xff0c;问了阿里云和宝塔客服&#xff0c;弄了将近2天&#xff0c;才找出问题出在哪里&#xff0c;在此记录一下问题的处理。 服…

深度探析:7天后不过期的微信群二维码生成的优势

在日常生活和工作中&#xff0c;微信不过期二维码深受用户的欢迎。因为传统的微信群二维码被下载下来后&#xff0c;只有7天有效期。但企业在日常运营中&#xff0c;如果直接使用下载下来的微信群二维码&#xff0c;会造成很多的不便和宣传资源浪费。这些问题&#xff0c;可以通…

华为ensp中ospf基础 原理及配置命令(详解)

CSDN 成就一亿技术人&#xff01; 作者主页&#xff1a;点击&#xff01; ENSP专栏&#xff1a;点击&#xff01; CSDN 成就一亿技术人&#xff01; ————前言———— OSPF 的全称是 Open Shortest Path First&#xff0c;意为“开放式最短路径优先”。是一种内部网关协…

MySQL之基本操作与用户授权

一 基本操作 1 SQL分类 数据库&#xff1a;database 表&#xff1a;table&#xff0c;行&#xff1a;row 列&#xff1a;column 索引&#xff1a;index 视图&#xff1a;view 存储过程&#xff1a;procedure 存储函数&#xff1a;function 触发器&#xff1a;trigger 事…

“Python神技:一键转换PPT页面为高清图片,源码大公开!”(附Python源码)

今天让claude3帮忙写了个python代码&#xff0c;实现了将ppt转换成图片功能。WPS中实现这个功能还需要开通会员&#xff0c; 其实也就一点代码就可以实现&#xff0c;而且powerpoint中还没有这个将页面转换成图片的功能&#xff0c;废话不多说&#xff0c;直接上源码。 import …

欧科云链:从技术与数据视角,看Solana如何成为Web3“流量担当”?

出品&#xff5c;欧科云链研究院 作者&#xff5c;Jason Jiang 坎昆升级完成后&#xff0c;除一众L2手续费锐减外&#xff0c;以太坊生态并未掀起涟漪&#xff0c;相反Solana凭借一波短暂的Meme热潮&#xff0c;再次成为焦点。尽管本周Solana生态的Meme热度褪去&#xff0c;但…

最新,955神仙公司名单(非外企)

955 神仙公司名单&#xff08;非外企&#xff09; 往常爆料最多的 955 神仙公司名单通常都是集中在一线城市的外企。 例如下面这张最为流行的名单图&#xff1a; 最近牛客网上有同学整理出了非外企的版本&#xff0c;其中不乏一些耳熟能详的互联网产品。 随手把名单分享给大家。…

SDKMAN多版本SDK并行管理工具

一、简介 SDKMAN是管理多个SDK并行版本的工具&#xff0c;它提供了方便的命令行界面&#xff08;CLI&#xff09;和API&#xff0c;用于列出&#xff0c;安装&#xff0c;切换和删除候选对象。此外&#xff0c;它还为我们设置了环境变量。 它还允许开发人员安装基于JVM的SDK&…