数据结构 —— BellmanFord算法

数据结构 —— BellmanFord算法

  • BellmanFord算法
  • 检测负权值环
  • BellmanFord和Dijkstra思想上的区别
      • Dijkstra算法的思想
      • Bellman-Ford算法的思想
      • 思想上的对比

我们今天来看一个算法BellmanFord算法,我们之前的Dijkstra算法只能用来解决正权图的单源最短路径问题。

Bellman-Ford算法是一种用于计算单源最短路径问题的算法,也就是说,它能找出一个图中某个特定顶点到所有其他顶点的最短路径。与Dijkstra算法不同,Bellman-Ford算法可以处理含有负权边的图,但不能处理包含负权环的图(因为从源点到包含负权环的任意点的距离可以无限减小)。

以下是Bellman-Ford算法的基本步骤:

  1. 初始化:将源点到自身的距离设为0,源点到其他所有顶点的距离设为无穷大。
  2. 放松操作:对图中的每条边进行V-1次放松操作,其中V是顶点的数量。在每次放松操作中,对于每条边(u, v),如果dist[v] > dist[u] + weight(u, v),则更新dist[v] = dist[u] + weight(u, v)。其中,dist[v]表示源点到v的当前最短路径长度,weight(u, v)表示边(u, v)的权重。
  3. 检测负权环:再进行一次边的放松操作。如果此时仍存在某条边(u, v)满足dist[v] > dist[u] + weight(u, v),则说明图中存在负权环。

我们先构建一个这样的图:
在这里插入图片描述在这里插入图片描述

BellmanFord算法

BellmanFord算法是站在全局的角度来思考问题,如果这条边可以通过另一条边得到一个更小的结果,就更新,基于这样的思想,我们可以暴力循环来解决:

		bool BellmanFord(const V& srci, vector<W>& dest, vector<int>& parentPath){//结点转化size_t srcIndex = FindSrci(srci);parentPath.resize(_vertex.size(), -1);dest.resize(_vertex.size(), MAX_W);dest[srcIndex] = W();for (size_t i = 0; i < _vertex.size(); i++){for (size_t j = 0; j < _vertex.size(); j++){if (_matrix[i][j] != MAX_W &&dest[j] > _matrix[i][j] + dest[i]){dest[j] = _matrix[i][j] + dest[i];parentPath[j] = i;}}}return true;}
	void TestGraphBellmanFord(){const char* str = "syztx";Graph<char, int, INT_MAX, true> g(str, strlen(str));g.AddEdge('s', 't', 6);g.AddEdge('s', 'y', 7);g.AddEdge('y', 'z', 9);g.AddEdge('y', 'x', -3);g.AddEdge('z', 's', 2);g.AddEdge('z', 'x', 7);g.AddEdge('t', 'x', 5);g.AddEdge('t', 'y', 8);g.AddEdge('t', 'z', -4);g.AddEdge('x', 't', -2);g.Print();vector<int> dist;vector<int> parentPath;g.BellmanFord('s', dist, parentPath);g.PrintShortestPath('s', dist, parentPath);}

在这里插入图片描述

我们发现路径是对的,但是权值不对
在这里插入图片描述
这是为什么呢?我们把选边过程挑出来:

bool BellmanFord(const V& srci, vector<W>& dest, vector<int>& parentPath){//结点转化size_t srcIndex = FindSrci(srci);parentPath.resize(_vertex.size(), -1);dest.resize(_vertex.size(), MAX_W);dest[srcIndex] = W();cout << "开始选边: " << endl;for (size_t i = 0; i < _vertex.size(); i++){for (size_t j = 0; j < _vertex.size(); j++){if (_matrix[i][j] != MAX_W &&dest[j] > _matrix[i][j] + dest[i]){cout << _vertex[i] << "->" << _vertex[j] << ":" << _matrix[i][j]<< endl;dest[j] = _matrix[i][j] + dest[i];parentPath[j] = i;}}}return true;}

在这里插入图片描述

问题就出在这两个地方:
在这里插入图片描述

在这里插入图片描述
我们之后的结果会对之前的结果有影响,所以我们还有套一层循环来保证我们的每条边都进行了更新:

			for (size_t k = 0; k < _vertex.size(); k++){for (size_t i = 0; i < _vertex.size(); i++){for (size_t j = 0; j < _vertex.size(); j++){if (_matrix[i][j] != MAX_W &&dest[j] > _matrix[i][j] + dest[i]){cout << _vertex[i] << "->" << _vertex[j] << ":" << _matrix[i][j]<< endl;dest[j] = _matrix[i][j] + dest[i];parentPath[j] = i;}}}}cout << endl;return true;}

在这里插入图片描述
这下权值就是对的,但是在更新过程中有些边是不用更新的,所以我们可以设计一个标志位来提高效率:

		bool BellmanFord(const V& srci, vector<W>& dest, vector<int>& parentPath){//结点转化size_t srcIndex = FindSrci(srci);parentPath.resize(_vertex.size(), -1);dest.resize(_vertex.size(), MAX_W);dest[srcIndex] = W();for (size_t k = 0; k < _vertex.size(); k++){bool exchange = false;cout << "开始选边: " << endl;for (size_t i = 0; i < _vertex.size(); i++){for (size_t j = 0; j < _vertex.size(); j++){if (_matrix[i][j] != MAX_W &&dest[j] > _matrix[i][j] + dest[i]){cout << _vertex[i] << "->" << _vertex[j] << ":" << _matrix[i][j]<< endl;dest[j] = _matrix[i][j] + dest[i];parentPath[j] = i;exchange = true;}}}if (exchange == false){break;}}cout << endl;return true;}

在这里插入图片描述

检测负权值环

如果这个图中有负权值环就会导致距离可以无限减小
在这里插入图片描述
所以我们的还有能力检测负权值环:

		bool BellmanFord(const V& srci, vector<W>& dest, vector<int>& parentPath){//结点转化size_t srcIndex = FindSrci(srci);parentPath.resize(_vertex.size(), -1);dest.resize(_vertex.size(), MAX_W);dest[srcIndex] = W();for (size_t k = 0; k < _vertex.size(); k++){bool exchange = false;cout << "开始选边: " << endl;for (size_t i = 0; i < _vertex.size(); i++){for (size_t j = 0; j < _vertex.size(); j++){if (_matrix[i][j] != MAX_W &&dest[j] > _matrix[i][j] + dest[i]){cout << _vertex[i] << "->" << _vertex[j] << ":" << _matrix[i][j]<< endl;dest[j] = _matrix[i][j] + dest[i];parentPath[j] = i;exchange = true;}}}if (exchange == false){break;}}for (size_t i = 0; i < _vertex.size(); ++i){for (size_t j = 0; j < _vertex.size(); ++j){// 检查有没有负权回路if (_matrix[i][j] != MAX_W&& dest[i] + _matrix[i][j] < dest[j]){return false;}}}return true;}

我们这里举个例子:

	void TestGraphBellmanFord(){const char* str = "syztx";Graph<char, int, INT_MAX, true> g(str, strlen(str));g.AddEdge('s', 't', 6);g.AddEdge('s', 'y', 7);g.AddEdge('y', 'z', 9);g.AddEdge('y', 'x', -3);g.AddEdge('z', 's', 2);g.AddEdge('z', 'x', 7);g.AddEdge('t', 'x', 5);g.AddEdge('t', 'y', -8); //修改g.AddEdge('t', 'z', -4);g.AddEdge('x', 't', -2);g.AddEdge('y', 's', 1); // 新增g.Print();vector<int> dist;vector<int> parentPath;if(g.BellmanFord('s', dist, parentPath))g.PrintShortestPath('s', dist, parentPath);elsecout << "存在负权回路" << endl;}

在这里插入图片描述

BellmanFord和Dijkstra思想上的区别

Bellman-Ford算法和Dijkstra算法在思想上的主要区别在于它们处理最短路径问题的方式以及它们对图中边权重的假设。下面详细解释这两种算法在思想上的差异:

Dijkstra算法的思想

Dijkstra算法基于贪心策略,它维护一个顶点集合S,其中包含了已经确定了从源点到这些顶点的最短路径的所有顶点。算法的核心思想是每次从未确定最短路径的顶点中选取距离源点最近的那个顶点加入集合S,并更新与之相邻的顶点的距离。

  1. 初始化:从源点开始,将其距离设为0,其他所有顶点的距离设为无穷大。
  2. 迭代过程:每次迭代选择未被访问过的、距离源点最近的顶点u,将u标记为已访问(加入S集合),并尝试通过u更新其所有未访问邻居的距离。如果通过u到达邻居v的总距离小于v当前记录的距离,则更新v的距离。
  3. 终止条件:当所有顶点都被访问过,或者当前最小距离的顶点距离为无穷大时,算法结束。

Bellman-Ford算法的思想

Bellman-Ford算法则采用了动态规划的思想,它通过逐步松弛所有的边,来逼近最短路径的正确解。算法的核心是重复执行“松弛”操作,直到不再有路径可以改进为止。

  1. 初始化:同样地,从源点开始,将其距离设为0,其他所有顶点的距离设为无穷大。
  2. 松弛操作:算法会遍历图中的所有边多次,每次遍历都尝试通过边的两端点更新路径距离。如果通过某条边可以得到更短的路径,就更新这条路径的距离。这个过程会重复V-1次(V为顶点数量),因为在任何无环图中,从源点到任意顶点的最短路径至多包含V-1条边。
  3. 负权重循环检测:在进行了V-1轮的松弛操作后,如果再次遍历所有边时还能进一步更新某个顶点的距离,那就意味着图中存在负权重循环。

思想上的对比

  • 适应性:Dijkstra算法假设所有边的权重都是非负的,而Bellman-Ford算法可以处理负权重边(只要不存在负权重循环)。
  • 效率:Dijkstra算法在处理无负权重边的图时通常比Bellman-Ford算法更高效,尤其是在使用优先队列优化的情况下。
  • 动态规划vs贪心策略:Bellman-Ford算法通过重复松弛所有边来逐渐逼近最短路径,体现了动态规划的思想;而Dijkstra算法通过每次选择局部最优解来逐步构建全局最优解,体现了贪心策略。

总体来说,Dijkstra算法和Bellman-Ford算法各自适用于不同的场景,选择哪个算法取决于图的特性和你对时间和空间效率的需求。

附上源码:

bool BellmanFord(const V& srci, vector<W>& dest, vector<int>& parentPath)
{// 将源点名称转换为其在顶点列表中的索引size_t srcIndex = FindSrci(srci);// 初始化parentPath向量,用于存储最短路径上的前驱顶点parentPath.resize(_vertex.size(), -1);// 初始化dest向量,用于存储源点到各顶点的最短距离dest.resize(_vertex.size(), MAX_W); // MAX_W代表无穷大// 设置源点到自身的距离为0dest[srcIndex] = W(); // W()应为权重类型的默认构造函数,通常为0// 开始Bellman-Ford算法的V-1轮松弛操作for (size_t k = 0; k < _vertex.size(); k++){bool exchange = false; // 用于检测本轮是否有路径更新cout << "开始选边: " << endl;// 遍历图中所有的边for (size_t i = 0; i < _vertex.size(); i++){for (size_t j = 0; j < _vertex.size(); j++){// 如果边(i, j)存在且通过边(i, j)可以得到更短的路径if (_matrix[i][j] != MAX_W && dest[j] > _matrix[i][j] + dest[i]){cout << _vertex[i] << "->" << _vertex[j] << ":" << _matrix[i][j]<< endl; // 输出更新的边信息dest[j] = _matrix[i][j] + dest[i]; // 更新dest[j]的值parentPath[j] = i; // 更新parentPath[j],记录前驱顶点exchange = true; // 标记发生了路径更新}}}// 如果一轮迭代中没有发生路径更新,则提前退出循环if (exchange == false){break;}}// 检查是否存在负权重循环for (size_t i = 0; i < _vertex.size(); ++i){for (size_t j = 0; j < _vertex.size(); ++j){// 如果通过边(i, j)可以进一步缩短路径,说明存在负权重循环if (_matrix[i][j] != MAX_W &&dest[i] + _matrix[i][j] < dest[j]){return false;}}}// 如果没有发现负权重循环,返回true,表示算法成功return true;
}

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

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

相关文章

C语言入门基础题:奇偶 ASCII 值判断(C语言版)和ASCII码表,什么是ASCII码,它的特点和应用?

1.题目描述&#xff1a; 任意输入一个字符&#xff0c;判断其 ASCII 是否是奇数&#xff0c;若是&#xff0c;输出 YES &#xff0c;否则&#xff0c;输出 NO例如&#xff0c;字符 A 的 ASCI 值是 65 &#xff0c;则输出 YES &#xff0c;若输入字符 B(ASCII 值是 66)&#xff…

数据库的学习(5)

题目&#xff1a; 1、新增员工表emp和部门表dept create table dept (deptl int,dept name varchar(11)) charsetutf8; create table emp (sid int,name varchar(11),age int,worktime start date,incoming int,dept2 int) charsetutf8; insert into dept values (101,财务), (…

嵌入式存储突破:STM32与W25Q64 Flash的高效SPI集成

摘要 在嵌入式系统设计中&#xff0c;数据存储解决方案对于确保数据的安全性、可靠性和快速访问至关重要。W25Q64 Flash存储器因其大容量和高效率成为STM32微控制器项目的首选存储设备之一。本文将详细介绍STM32与W25Q64 Flash存储器的高效SPI集成方法&#xff0c;包括硬件设计…

技术探索之kotlin浅谈

Kotlin是一种静态类型编程语言&#xff0c;它运行在Java虚拟机&#xff08;JVM&#xff09;上&#xff0c;可以与Java代码互操作。Kotlin由JetBrains开发&#xff0c;是一种现代、简洁且安全的编程语言。它在2011年首次亮相&#xff0c;2017年被谷歌宣布为Android官方开发语言。…

Matlab中如何添加OptiluX?

1、打开Matlab&#xff0c;依次点击“新建”&#xff0c;“工程”&#xff0c;“从SVN”。 2、存储库路径输入&#xff1a; p/optilux/code - Revision 80: /trunk 同时在“源代码控制集成”菜单中选择“SVN (1.9)” 3、沙盒选择一个自己建的文件夹即可。 来源&#xff1a;Opt…

特征值究竟体现了矩阵的什么特征?

特征值究竟体现了矩阵的什么特征&#xff1f; 简单来说就是x经过矩阵A映射后和自己平行 希尔伯特第一次提出eigenvalue,这里的eigen就是自己的。所以eigenvalue也称作本征值 特征值和特征向量刻画了矩阵变换空间的特征 对平面上的任意向量可以如法炮制&#xff0c;把他在特征…

Spring Boot常用注解类

常用注解类 packge org.springframework.boot.autoconfigure EnableAutoConfiguration Enable auto-configuration of the Spring Application Context, attempting to guess and configure beans that you are likely to need. Auto-configuration classes are usually app…

集创北方ICN6202 低功耗MIPIDSI转2 PORT LVDS 支持1080P分辨率,成熟批量产品

ICN6202描述&#xff1a; ICN6202是一个接收MIPIDSI输入和发送LVDS输出的桥接芯片。MIPIDSI最多支持4个车道&#xff0c;每个车道的最大运行频率为1Gbps&#xff1b;总最大输入带宽为4Gbps&#xff1b;并且还支持MIPI定义的ULPS&#xff08;超低功耗状态&#xff09;。ICN6202…

Elasticsearch集群搭建

集群概念 在单台 ES 服务器上&#xff0c;随着一个索引内数据的增多&#xff0c;会产生存储、效 率、安全等问题。 因此引入集群 我们需要将索引拆分成多份&#xff0c;分别放入不同的服务器中&#xff0c;此时这几台服务器维护了同一个索引&#xff0c;我们称这几台服务器为一…

计算机毕业设计Python深度学习游戏推荐系统 Django PySpark游戏可视化 游戏数据分析 游戏爬虫 Scrapy 机器学习 人工智能 大数据毕设

本论文的主要研究内容如下&#xff1a; 了解基于Spark的TapTap游戏数据分析系统的基本架构&#xff0c;掌握系统的开发方法&#xff0c;包括系统开发基本流程、开发环境的搭建、测试与运行等。 主要功能如下&#xff1a; &#xff08;1&#xff09;用户管理模块&#xff1a…

vue3 JS 调用 Android 原生方法

在Vue 3中调用Android原生方法通常涉及到WebView与原生代码的交互。你可以使用WebView的JavaScript接口来实现这一点。以下是一个简化的步骤和示例代码&#xff1a; 在Android端&#xff0c;创建一个类继承自WebView并实现JavaScriptInterface。在这个类中&#xff0c;定义一个…

初阶数据结构—排序

第一章&#xff1a;排序的概念及其运用 1.1 排序的概念 排序&#xff1a;所谓排序&#xff0c;就是使一串记录&#xff0c;按照其中的某个或某些关键字的大小&#xff0c;递增或递减的排列起来的操作。 稳定性&#xff1a;假定在待排序的记录序列中&#xff0c;存在多个具有…

LIO-SAM编译ubuntu20.04 Noetic

一、下载 mkdir -p ~/lio_sam_ws/src cd ~/lio_sam_ws/src git clone https://github.com/TixiaoShan/LIO-SAM.git cd ..二、编译&&解决报错 catkin_make报错如下 解决方案&#xff1a; 第一步&#xff1a; sudo add-apt-repository ppa:borglab/gtsam-release-4…

云端墨迹:在iCloud上构筑您的个人博客或网站内容存储堡垒

云端墨迹&#xff1a;在iCloud上构筑您的个人博客或网站内容存储堡垒 在数字化时代&#xff0c;个人博客和网站成为表达思想、分享知识和展示创意的重要平台。iCloud作为苹果公司提供的云服务&#xff0c;提供了一个安全、可靠且易于使用的存储解决方案&#xff0c;让您可以在…

数学建模美赛经验小结

图片资料来自网络所听讲座&#xff0c;感谢分享&#xff01;

网络编程的学习之udp

Udp编程过程 Sento不会阻塞 实现聊天室效果 上线 聊天 下线 服务端需要一个地址&#xff0c;去保留名字和ip地址 交互的时候发结构体 下面这个宏只能在c语言里使用 ser.sin_port htons(50000); 上面是端口号50000以上&#xff0c;两边要一样 这里是不要让udp发的太快&am…

CleanCode、安全编码规范

Clean Code 规范 Clean Code 是由 Robert C. Martin 提出的编写高质量代码的原则。主要包括以下几点 有意义的命名&#xff1a; 命名要准确和清晰&#xff0c;让人一看就知道变量、函数或类的用途。避免使用缩写和难以理解的名称 // 不好的命名 val d: Int 5// 好的命名 v…

顺序结构 ( 三 ) —— 常量和变量 【互三互三】

常量 一、常量概述 常量是指在程序中使用的一些具体的数、字符。在程序运行过程中&#xff0c;其值不能被更改。如123,145.88,m,TRUE等。 1、整型常量&#xff1a;如3、-5、0等。 整型常量是表示整数的常量。有三种表示形式&#xff1a; 1&#xff09;十进制形式。如9…

Unity Shader学习笔记

Shader类型 类型详情Standard Surface Shader标准表面着色器&#xff0c;基于物理的着色系统&#xff0c;用于模拟各种材质效果&#xff0c;如石头、木材、玻璃、塑料和金属等。Unlit Shader最简单的着色器&#xff0c;不包含光照但包含雾效&#xff0c;只由最基础的Vertex Sh…

【Vision Pro开发】小白开发如何过渡到visionOS开发 - SOP清单

为什么Unity开发者应该考虑学习Vision OS的原生开发,并解释了开发过程中面临的挑战和优势。我整理了一些关键点,对于刚进入这个赛道的小白开发者可以参考: 1. Vision Pro开发平台选择 - 主要有三种选择:原生开发(Xcode+SwiftUI+RealityKit)、Unity开发和WebXR开发 - 原生开…