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

 

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

                                              ♈️今日夜电波:アンビバレント—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! 

                                       

                                                                        给个三连再走嘛~  

 

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

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

相关文章

电脑文件msvcp100.dll丢失原因,如何快速修复msvcp100.dll

电脑文件msvcp100.dll丢失原因&#xff0c;最近有朋友在问这个&#xff0c;显然会问这个的人&#xff0c;一般都是遇到了msvcp100.dll丢失的问题了&#xff0c;今天我们就来详细的给大家说说msvcp100.dll这个文件吧&#xff0c;我们只有了解了msvcp100.dll这个文件&#xff0c;…

uniapp 云开发省钱之调整函数执行内存大小

我这个5块钱一个月的服务空间配置&#xff1a; 现在还只有少量的用户和自己测试之用&#xff0c;目前消耗的情况&#xff1a; 云函数的使用量还是挺高的&#xff0c;目前还是正好能覆盖一个月的使用量&#xff0c;等用户量上来肯定是不行的&#xff0c;所以得想想办法压榨一下云…

Docker 笔记(七)--打包软件生成镜像

目录 1. 背景2. 参考3. 文档3.1 使用docker container commit命令构建镜像3.1.1 [Docker官方文档-docker container commit](https://docs.docker.com/reference/cli/docker/container/commit/)Description&#xff08;概述&#xff09;Options&#xff08;选项&#xff09;Exa…

软考 网络工程师 每日学习打卡 2024/3/21

学习内容 第8章 网络安全 本章主要讲解网络安全方面的基础知识和应用技术。针对考试应该掌握诸如数据加密、报文认 证、数字签名等基本理论&#xff0c;在此基础上深入理解网络安全协议的工作原理&#xff0c;并能够针对具体的 网络系统设计和实现简单的安全解决方案。 本章共有…

一触即发,全栈联动:使用Docker Compose部署Spring Boot应用+MySQL+Redis实战指南

在云原生时代的快车道上&#xff0c;Docker Compose无疑是那辆助您疾驰的豪华跑车&#xff0c;它凭借其简洁高效的YAML配置文件&#xff0c;让您能够轻松部署和管理包含Spring Boot应用、MySQL数据库以及Redis缓存服务在内的完整堆栈。本文将深入浅出地引导您通过一个docker-co…

蓝桥杯:Python基础学习一

目录 一、遍历列表 1.使用for 循环和 enumerate()函数实现 2.案例代码 二、对列表进行统计和计算 1.统计数值列表的元素和 2.案例代码 三、对列表进行排序 1.使用列表对象的sort()方法 2.使用内置的 sorted()函数实现 四、列表推导式 1.从列表中选择符合条件的元素组…

WMI接口设计实现

WMI是Windows操作系统管理数据和操作的基础设施&#xff0c;系统管理员可以使用VB Script、PowerShell及Windows API&#xff08;C、C#等&#xff09;管理本地或远程计算机。 使用WMI框架应用程序可以直接访问EC RAM、 I/O端口、Memory地址、寄存器、Setup NV设定值&#xff0c…

unicloud 云函数 介绍及使用

普通云函数 callFunction方式云函数&#xff0c;也称之为普通云函数。 uni-app的前端代码&#xff0c;不再执行uni.request联网&#xff0c;而是通过uniCloud.callFunction调用云函数。 callFunction方式避免了服务器提供域名&#xff0c;不暴露固定ip&#xff0c;减少被攻击…

京东商品详情页数据抓取:探索背后的技术与合法途径

京东的商品详情页面数据通常是通过其API进行获取的&#xff0c;但是京东的API并不是公开的&#xff0c;需要注册京东开放平台并获取相应的API密钥。此外&#xff0c;直接爬取京东网站的数据可能违反了京东的服务条款&#xff0c;并且可能涉及到法律问题。 如果你确实有合法的需…

提升商品销量必备!淘宝商品评论电商API接口全解析

评论是电商销售中至关重要的一环&#xff0c;它能直接影响到商品销量。淘宝商品评论电商API接口的全面了解和合理的应用&#xff0c;将成为提升销量的利器。联讯数据将从不同角度详细解析淘宝商品评论电商API接口&#xff0c;为您揭示成功提升商品销量的关键。 淘宝商品评论电…

手写简易操作系统(十四)--内存管理系统

前情提要 前面我们实现了一个简单的C库&#xff0c;现在我们将实现一个内存池。 之前我们的内存都是自己规划的&#xff0c;我们需要 0xc0001500 这个地址&#xff0c;就将程序放在哪儿。但是程序多了怎么办&#xff1f;还需要我们自己去一个一个安排位置吗&#xff0c;有一块…

四、分布式锁之自定义分布式锁

1、基本原理和实现方式对比 分布式锁&#xff1a;满足分布式系统或集群模式下多个进程可见并且互斥的锁。分布式锁的核心思想就是多线程都使用同一把锁&#xff0c;实现程序串行执行。 分布式锁需要具备的条件&#xff1a; 特性含义可见性多个线程都能感知到变化互斥性分布…

Orange3数据预处理(行选择组件)

选择行 根据数据特征的条件选择数据实例。 输入 数据&#xff1a;输入数据集 输出 匹配数据&#xff1a;满足条件的实例 不匹配数据&#xff1a;不满足条件的实例 数据&#xff1a;带有额外列的数据&#xff0c;显示实例是否被选中 这个小部件根据用户…

数据库系统概论-第16章 数据仓库与联机分析处理技术

概念性的介绍&#xff0c;一略而过&#xff0c;不重要。 16.1 数据仓库技术 16.2 联机分析处理技术 16.3 数据挖掘技术 16.4 大数据时代的新型数据仓库 16.5 小结

jetson nano torch1.6 torchvision0.7.0 yolov5

pytorch版本对应关系查看网址&#xff1a; pytorch torchvision pytorch安装方式 点击pytorch链接&#xff1a;pytorch torchvision安装方式 sudo apt-get install libjpeg-dev zlib1g-dev libavcodec-dev libavformat-dev libswscale-dev git clone --branch v0.7.0 https…

第113讲:Mycat实践指南:按照单位为天的日期实现水平分表

文章目录 1.按天分片的概念1.按天分片的概念 2.按照天数对某张表进行水平拆分2.1.在所有的分片节点中创建表结构2.2.配置Mycat实现字符串按天分片的水平分表2.2.1.配置Schema配置文件2.2.2.配置Rule分片规则配置文件2.2.3.配置Server配置文件2.2.4.重启Mycat 2.3.写入数据观察分…

[每周一练][NewStarCTF 2023 公开赛道]EasyLogin

一打开是个登录界面&#xff0c;注册账号进去看了一下似乎没有什么提示。按照经验这种登录系统的一般就是sql或者爆破。先试试简单的爆破。 猜测管理员账号&#xff1a;admin,密码&#xff1a;123456。抓包看到传入的密码是被加密了的。应该是MD5加密。 爆破的话就必须用MD5的密…

短视频矩阵系统/短视频矩阵系统/自研独立框架

短视频矩阵系统/短视频矩阵系统/自研独立框架&#xff0c; 短视频综合矩阵营销管理系统,一键分发多个平台,帮助企业管理海量视频账号&#xff0c;包含抖音视频、AI混剪、矩阵导流&#xff0c;客户获取等功能。通过将视频分发、到各账号&#xff0c;提高品牌曝光率、且可以同时管…

系列直播预告:Apache Doris 2.1 新版本特性解读来袭,惊喜周边等你拿!

不久之前&#xff0c;Apache Doris 2.1.0 版本迎来正式发布&#xff0c;在盲测性能提升 100% 的同时&#xff0c;更在数据湖分析、半结构化数据分析、数据写入与更新、数据存储与负载隔离等方面推出众多核心特性&#xff0c;实时性和易用性的到全面提升。 为了让更多关注和喜爱…

transformer的学习:Attention is all you need

目录 整体概述&#xff1a;​编辑​编辑 encoder&#xff1a; embedding&#xff1a; ​编辑 self-attention&#xff1a; 向量的相似度计算&#xff1a; qkv怎么来的​编辑 softmax&#xff1a; code multi-head-attention 位置编码&#xff1a; 残差&&FFN&…