数据结构 —— 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,财务), (…

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;把他在特征…

集创北方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…

初阶数据结构—排序

第一章&#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…

数学建模美赛经验小结

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

网络编程的学习之udp

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

Unity Shader学习笔记

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

30. 梯度下降法及其应用

1. 引言 在深度学习中&#xff0c;损失函数的求解是一个关键步骤。损失函数通常没有解析解&#xff0c;因此需要通过最优化算法来逼近求解。其中&#xff0c;梯度下降法是最常用的优化算法之一。本文将详细介绍梯度下降法的基本概念、理论基础、及其在深度学习中的应用。 2. …

甄选范文“论基于构件的软件开发方法及其应用”,软考高级论文,系统架构设计师论文

论文真题 基于构作的软件开发 (Component-Based Software Development,CBSD) 是一种基于分布对象技术、强调通过可复用构件设计与构造软件系统的软件复用途径。基于构件的软件系统中的构件可以是COTS (Commercial-Off-the-Shelf)构件,也可以是通过其它途径获得的构件(如自…

2970.力扣每日一题7/10 Java(暴力枚举)

博客主页&#xff1a;音符犹如代码系列专栏&#xff1a;算法练习关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ 目录 解题思路 解题方法 时间复杂度 空间复杂度 Code 解题思路 incre…

图论---无向图中国邮路的实现

开始编程前分析设计思路和程序的整体的框架&#xff0c;以及作为数学问题的性质&#xff1a; 程序流程图&#xff1a; 数学原理&#xff1a; 本质上是找到一条欧拉回路&#xff0c;考虑图中的边权重、顶点的度数以及如何通过添加最少的额外边来构造欧拉回路&#xff0c;涉及到欧…

链表 OJ(一)

移除链表元素 题目连接&#xff1a; https://leetcode.cn/problems/remove-linked-list-elements/description/ 使用双指针法&#xff0c;开始时&#xff0c;一个指针指向头节点&#xff0c;另一个指针指向头节点的下一个结点&#xff0c;然后开始遍历链表删除结点。 这里要注…

推荐一款功能强大的 GPT 学术优化开源项目GPT Academic:学术研究的智能助手

今天&#xff0c;我将向大家介绍一个强大的开源项目—GPT Academic&#xff0c;它或许正是你一直在寻找的理想工具。 已一跃成为 60.4k Star 的热门项目 GPT Academic 目前在 GitHub 上已经揽获了 60.4k 的 Star&#xff0c;这不仅反映了它的受欢迎程度&#xff0c;更证明了它…

硅纪元AI应用推荐 | 百度橙篇成新宠,能写万字长文

“硅纪元AI应用推荐”栏目&#xff0c;为您精选最新、最实用的人工智能应用&#xff0c;无论您是AI发烧友还是新手&#xff0c;都能在这里找到提升生活和工作的利器。与我们一起探索AI的无限可能&#xff0c;开启智慧新时代&#xff01; 百度橙篇&#xff0c;作为百度公司在202…

【网络安全】Oracle:SSRF获取元数据

未经许可&#xff0c;不得转载。 文章目录 前言正文漏洞利用 前言 Acme 是一家广受欢迎的播客托管公司&#xff0c;拥有庞大的客户群体。与许多大型运营公司一样&#xff0c;Acme 采用了Apiary的服务&#xff0c;使用户能够安全高效地管理他们的播客。 Apiary 于2017年初被Or…