图详解第五篇:单源最短路径--Bellman-Ford算法

文章目录

  • 单源最短路径--Bellman-Ford算法
    • 1. 算法思想
    • 2. 图解
    • 3. 代码实现
    • 4. 测试
    • 5. 优化
      • 循环的提前跳出
      • 队列优化
    • 6. 负权回路(负权环)判定
    • 7. 源码

Dijkstra算法只能用来解决正权图的单源最短路径问题,但有些题目会出现负权图。这时这个算法就不能帮助我们解决问题了,而bellman—ford(贝尔曼-福特)算法可以解决负权图的单源最短路径问题,那这篇文章我们就来学习一下Bellman-Ford算法

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

1. 算法思想

Bellman-Ford是一种比较暴力的求解更新:

它对图进行 V-1 次迭代(其实是最多V-1次,至于为什么是V-1次后面会解释到),其中 V 是图中顶点的数量。
在每次迭代时,遍历图中的所有的顶点,并对每个顶点的相邻顶点进行松弛操作,松弛我们上一篇文章解释过了,即对当前顶点的每一个相邻结点v ,判断源节点s到当前结点u 的代价与u 到v 的代价之和是否比原来s 到v 的代价更小,若代价比原来小则要将s 到v 的代价更新为s 到u 与u 到v 的代价之和,否则维持原样,从而不断更新每个顶点的最短路径。
当然如果某次迭代之后不在有新的距离更新,也可以提前结束,这个我们在后面的优化会提到。

贝尔曼-福特算法与迪杰斯特拉算法类似:

都以松弛操作为基础,即估计的最短路径值渐渐地被更加准确的值替代,直至得到最优解。
只不过迪杰斯特拉算法每次去选到起点最短的边,然后去向外扩展更新(即对这条边的终止顶点进行松弛),直至所有的边都更新一遍就可以得到结果(因为它每次选的都是最小的);
而贝尔曼-福特算法不管边的大小,就是比较暴力的把所有的边都更新(即先后对所有的顶点的相邻顶点进行松弛),也因此它一遍过后可能得不出最短的路径,可能需要进行多次迭代。

它的优点是可以解决有负权边的单源最短路径问题,而且可以用来判断是否有负权回路。
它也有明显的缺点,它的时间复杂度 O(N*E) (N是点数,E是边数)普遍是要高于Dijkstra算法O(N²)的。像这里如果我们使用邻接矩阵实现,那么遍历所有边的数量的时间复杂度就是O(N^3)。

2. 图解

那下面我们还是对照着图再给大家走一遍Bellman-Ford算法的过程

在这里插入图片描述
就以这个图为例,我们来走一遍整个过程,当然上面这个图可能没有每一步都画出来,不是特别详细

那我们来走一个详细的:

这是起始状态,s位为源点在这里插入图片描述
那首先对s的相邻顶点进行松弛(当然实际中应该是按照顶点在邻接矩阵里面存储的顺序去遍历的,那其实这个图和我们上一篇文章讲Dijkstra算法的那个图顶点都是一样的,只是权值不同,所以下面我们就按之前的存储顺序去走)
那s的话这里他连出去的两条边肯定都会更新(因为源节点s到当前结点u(这里就是s) 的代价与u 到v 的代价之和是否比原来s 到v 的代价更小)
那第一次松弛之后就是这样:
在这里插入图片描述
那然后对y的相邻顶点进行松弛
在这里插入图片描述
接着是z
在这里插入图片描述
z只连出去一条边z->x,但是这里不会更新,因为s->z+z->x的距离大于目前s->x的距离
接着是t
在这里插入图片描述
t连出去了两条t->z和t->x,只更新t->z,那之前的s->y->z(16)就不再是z目前的最短路径了(当然实际写代码中的话我们的两个数组:路径距离/权值的数组dist 和 存储路径的数组pPath就也需要相应的修改)
最后是x
在这里插入图片描述
x只连出去一条边x->t,进行更新

那现在其实一趟迭代就完成了,所有顶点的相邻顶点都完成了一次松弛

我们现在得到的是这样一个样子:

在这里插入图片描述
其实就是最开始给大家看的图里面倒数第二个
在这里插入图片描述

可是最后一幅才是最终结果啊:

是的,所以我们上面也说了:
Bellman-Ford算法是比较暴力的把所有的边都更新(即先后对所有的顶点的相邻顶点进行松弛),而不像Dijkstra算法的贪心那样每次选的都是最短的,也因此它一遍过后可能得不出最短的路径,可能需要进行多次迭代

那我们就继续进行第二遍迭代:

第二遍起始状态就是这样的在这里插入图片描述
首先第一个还是s顶点,那这里没有边需要更新;
然后是y,也没有边更新;
接着z,也没有更新;
再下面t,更新一条:现在t的最短路径是2,t->z是-4,所以z的最短路径更新为-2
在这里插入图片描述
我们看到这次边并没有变化,只是权值更新了;
因为上一轮z的最短路径确定为s->t->z:2(此时t的最短路径是s->t:6)之后,t的最短路径又被更新成了s->y-x-t:2
t的最短路径变了,导致z的最短路径也变了,但是在第二轮才更新出来
最后顶点x,也没有边更新
在这里插入图片描述
此时就得到了最终结果,所以我们看到这里这个图起始更新了两轮(后面代码写出来我们也可以验证一下是不是两轮)就得到了最终结果,后续即使再进行迭代,也不会有新的路径更新了。
所以我们说了可以提前结束,不一定非要迭代V-1 次, V 是图中顶点的数量

那我们也来说一下,为什么最多要迭代V-1次呢?

因为如果一个图有V个顶点,那图中路径最多也只经过V-1条边,所以V-1次迭代可以保证所有顶点的最短路径被正确地计算更新
当然我们可以通过判断提前结束。

3. 代码实现

那下面我们就来写写代码:

首先前面的部分和Dijkstra算法是一样的在这里插入图片描述
我就不过多解释了
那然后我们就迭代更新就行了
在这里插入图片描述

4. 测试

来测试一下:

在这里插入图片描述
这个测试用例就是我们上面讲解的时候用的图,来看一下对不对
在这里插入图片描述
对照一下
在这里插入图片描述
没有问题

我们还可以验证一下是不是更新了两轮就完成了:

加个打印看一下
在这里插入图片描述
再来运行
在这里插入图片描述
没有问题,只有前两轮有更新,而且大家可以对照一下,更新的边跟我们上面分析的都是一致的

5. 优化

循环的提前跳出

首先第一个优化就是我们上面提到的:

Bellman-Ford算法最多对图进行V-1次迭代,但是如果某次迭代之后不在有新的距离更新,我们就可以提前结束循环。

那这个优化很好做到:

在循环中设置一个判定就行了
在这里插入图片描述
在这里插入图片描述

队列优化

西南交通大学的段凡丁于1994年提出了用队列来优化的算法:

松弛操作必定只会发生在最短路径前导节点松弛成功过的节点上。
通过使用队列来存储需要进行松弛操作的顶点,可以减少不必要的重复操作。在每次迭代中,只需要对队列中的顶点进行松弛操作,而不用遍历整个图。

解释一下:

就比如我们上面那个图的例子,它其实只有前两轮进行了更新,而且第二轮的时候只对结点t进行了松弛更新
在这里插入图片描述
为什么呢?
因为第一轮t的最短路径更新为s->t(6)之后,后面又变成了s->y-x-t(2)
在这里插入图片描述
那它就可能影响它的相邻顶点的最短路径
在这里插入图片描述
而除了t之外的其它顶点第二轮并没有真正松弛更新,虽然进行了判断

所以就可以进行队列优化:

搞一个队列,第一轮让所有顶点都入队列,第二轮就只让第一轮更新出更短路径的顶点入队列(后续也是如此),只对队列中的顶点进行松弛操作,就可以避免冗余计算。

那这个优化大家有兴趣可以自己实现一下

6. 负权回路(负权环)判定

那除此之外呢还有一个问题:

虽然Bellman-Ford算法可以解决负权图的单源最短路径问题,但是对于图中有负权回路/环(即图中存在环/回路,且环的权值之和为负值)的情况,Bellman-Ford算法也无能为力,这种情况是求不出最短路径的!

因为如果有负权环的话,某些顶点的最小路径是可以一直往小去更新的:

比如
在这里插入图片描述
s->y的距离,如果走s->t->y的话是-2,但是如果从y再顺时针绕一圈就变成-3了,再绕就是-4,可以一直减小,无限制的降低总代价。
当然现在这个图里不止这一个负权环。

我们可以来测试一下:

在这里插入图片描述
这个测试用例其实就对应上面的图
运行一下
在这里插入图片描述
当然这里我们外层循环直接控制了它最多迭代n-1次(n为顶点个数),所有这里迭代n-1次就停了。
但是要注意这里的结果是不对的,因为如果我们让它继续迭代的话,有些路径是可以继续缩小的。
如果我们不控制外层循环的话,它应该就要死循环的迭代更新了
在这里插入图片描述
而且我们把打印放开的话会看到他这里就找路径的时候就死循环卡在这里了
在这里插入图片描述
大家可以调式观察一下,肯定就是往上找路径的时候陷入到环里面了
在这里插入图片描述

那我们可以想办法检测一下这种情况:

其实很好办:
如果在进行了n-1次迭代之和还能更新,就是带负权回路的情况
因为如果不带负权回路的情况,最多迭代n-1次就可以得到最终结果
在这里插入图片描述
这样我们就可以通过返回值判断是否带负权环

测试一下:

在这里插入图片描述
就判断出来了
如果还把图改回原来的
在这里插入图片描述
没问题!

7. 源码

bool BellmanFord(const V& src, vector<int>& pPath, vector<W>& dist)
{//初始化一下记录路径和权值(距离)的数组size_t srci = GetVertexIndex(src);size_t n = _vertexs.size();pPath.resize(n, -1);dist.resize(n, MAX_W);pPath[srci] = srci;dist[srci] = W();//最多迭代n-1次for (size_t k = 0; k < n - 1; k++){//一趟迭代的逻辑cout << "第" << k + 1 << "轮迭代:" << endl;bool update = false;//依次遍历所有顶点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]){update = true;cout << _vertexs[i] << "->" << _vertexs[j] << ":" << _matrix[i][j] << endl;dist[j] = dist[i] + _matrix[i][j];//同时更新记录路径的数组pPathpPath[j] = i;}}}if (update == false)break;}//如果进行了n-1次迭代之和还能更新,就是带负权回路的情况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;
}
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);vector<int> dist;vector<int> parentPath;g.BellmanFord('s', dist, parentPath);g.ptintMinPath('s', dist, parentPath);*/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('y', 's', 1); // 新增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);vector<int> dist;vector<int> parentPath;if (g.BellmanFord('s', dist, parentPath))g.ptintMinPath('s', dist, parentPath);elsecout << endl << "带负权回路!!!" << endl;
}

在这里插入图片描述

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

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

相关文章

服务器中了locked勒索病毒怎么办,勒索病毒解密,数据恢复

最近一段时间内&#xff0c;相信很多使用金蝶或用友的办公软件的企业&#xff0c;有很多都经历了locked勒索病毒的攻击&#xff0c;导致企业服务器被加密无法正常使用&#xff0c;严重影响了企业的正常工作。通过云天数据恢复中心的解密恢复发现&#xff0c;在今年locked勒索病…

页面查询多项数据组合的线程池设计 | 京东云技术团队

背景 我们应对并发场景时一般会采用下面方式去预估线程池的线程数量&#xff0c;比如QPS需求是1000&#xff0c;平均每个任务需要执行的时间是t秒&#xff0c;那么我们需要的线程数是t * 1000。 但是在一些情况下&#xff0c;这个t是不好估算的&#xff0c;即便是估算出来了&…

解决 sharp: Installation error: unable to verify the first certificate

使用 plasmo 时报错如下&#xff1a; E:\chromeplugins>pnpm create plasmo ../.pnpm-store/v3/tmp/dlx-46852 | 2 ../.pnpm-store/v3/tmp/dlx-46852 | Progress: resolved 2, reused 2, downloaded 0, added 2, done &#x1f7e3; Plasmo v0.83.0 &…

华为---企业WLAN组网基本配置示例---AC+AP组网

ACAP组网所需的物理条件 1、无线AP---收发无线信号&#xff1b; 2、无线控制器(AC)---用来控制管理多个AP&#xff1b; 3、PoE交换机---能给AP实现网络连接和供电的交换机&#xff1b; 4、授权&#xff1a;默认AC管理的AP数量有限&#xff0c;买授权才能管控更多AP。 WLAN创建…

苹果开发者 Xcode发布TestFlight全流程

打包前注意事项 使用Xcode导出安装包之前&#xff0c;必须先确认账户的所有合约是否全部同意&#xff0c;如果有不同意的&#xff0c;在出包的时候会弹出报错 这是什么意思 这意味着您有一些需要在应用商店连接上验证的协议(protocol)/契约(Contract)。解决方案 连接到应用商店…

百度的新想象力在哪?

理解中国大模型&#xff0c;百度是一个窗口。这个窗口的特殊性不仅在于变化本身&#xff0c;而是在于百度本身就是那个窗口。 作者|皮爷 出品|产业家 沿着首钢园北区向西北步行10分钟&#xff0c;就能看到一个高约90米的大跳台&#xff0c;在工业园钢铁痕迹的印衬下&#…

Vue-vue项目Element-UI 表单组件内容要求判断

整体添加判断 <el-formref"ruleFormRef":model"formModel"class"demo-ruleForm"label-position"top"status-icon:rules"rules"><el-form-item label"姓名" prop"applyUsers" class"form-…

[云原生1.]Docker数据管理与Cgroups资源控制管理

文章目录 1. Docker的数据管理1.1 数据卷1.1.1 示例 1.2 数据卷容器 2. 容器互联3. Cgroups资源控制管理3.1 简介3.2 cgroups的主要功能3.3 cpu时间片的简单介绍3.4 对CPU使用的限制3.4.1 对CPU使用的限制&#xff08;基于单个容器&#xff09;3.4.2 对CPU使用的限制&#xff0…

vue3中computed的用法

一、完整代码 <template><div class"about"><h1>Computed的用法</h1><h3>姓:{{ person.firstName }}</h3><input type"text" v-model"person.firstName"><h3>名:{{ person.lastName }}</h3…

【PXIE301-211】基于PXIE总线的16路并行LVDS数据采集、4路低速、2路隔离RS422数据处理平台

板卡概述 PXIE301-211A是一款基于PXIE总线架构的16路高速LVDS、4路低速LVDS采集、2路隔离RS422数据处理平台&#xff0c;该平台板卡采用Xilinx的高性能Kintex 7系列FPGA XC7K325T作为实时处理器&#xff0c;实现各个接口之间的互联。板载1组64位的DDR3 SDRAM用作数据缓存。板卡…

【UE】纯蓝图实现:在游戏运行时设置关键点,然后让actor沿着关键点移动

前言 在上一篇博客(【UE】两步实现“从UI中拖出Actor放置到场景中”)中我们已经实现了如何从UI拖拽生成Actor ,本篇博客在此基础上要实现的是:从UI中拖出车,再从UI中拖出关键点,点击“开始移动”按钮后,车会沿着关键点移动,具体效果如下所示。 效果 步骤 1. 首先创建…

Flink学习之旅:(三)Flink源算子(数据源)

1.Flink数据源 Flink可以从各种数据源获取数据&#xff0c;然后构建DataStream 进行处理转换。source就是整个数据处理程序的输入端。 数据集合数据文件Socket数据kafka数据自定义Source 2.案例 2.1.从集合中获取数据 创建 FlinkSource_List 类&#xff0c;再创建个 Student 类…

ps或游戏提示d3dcompiler_47.dll缺失怎么修复?常见的修复方法总结

在当今这个信息化的时代&#xff0c;计算机已经成为我们生活和工作中不可或缺的一部分。然而&#xff0c;随着软件的不断更新和升级&#xff0c;一些技术问题也时常困扰着我们。其中&#xff0c;d3dcompiler_47.dll缺失就是一个常见的问题。本文将详细介绍五种修复方案&#xf…

性能超越 Clickhouse | 物联网场景中的毫秒级查询案例

1 物联网应用场景简介 物联网&#xff08;Internet of Things&#xff0c;简称 IoT&#xff09;是指通过各种信息传感、通信和 IT 技术来实时连接、采集、监管海量的传感设备&#xff0c;从而实现对现实世界的精确感知和快速响应&#xff0c;继而实现自动化、智能化管理。在查…

Visual Studio2019 与 MySQL连接 版本关系

Refer: VS 连接MySQL | mysql-for-visualstudio 的安装-CSDN博客 【精选】用VS2019&#xff08;C#&#xff09;连接MYSQL(从0入门&#xff0c;手把手教学&#xff09;_mysql-for-visualstudio-1.2.9.msi_Flying___rabbit的博客-CSDN博客 一、工具&#xff1a;VS2019需要连接M…

gin框架39--重构 BasicAuth 中间件

gin框架39--重构 BasicAuth 中间件 介绍gin BasicAuth 解析自定义newAuth实现基础认证注意事项说明 介绍 每当我们打开一个网址的时候&#xff0c;会自动弹出一个认证界面&#xff0c;要求我们输入用户名和密码&#xff0c;这种BasicAuth是最基础、最常见的认证方式&#xff0…

RabbitMQ整理

MQ(Message Queue)&#xff1a;是队列&#xff0c;也是跨进程的通信机制&#xff0c;用于上下游传递信息 FIFO(First In First Out)&#xff1a;先进先出 RabbitMQ访问&#xff1a;http://127.0.0.1:15672/ 默认账号密码&#xff1a;guest 优势&#xff1a;流量削峰&#x…

CRC16计算FC(博途SCL语言)

CRC8的计算FC,相关链接请查看下面文章链接: 博途SCL CRC8 计算FC(计算法)_博途怎么计算crc_RXXW_Dor的博客-CSDN博客关于CRC8的计算网上有很多资料和C代码,这里不在叙述,这里主要记录西门子的博途SCL完成CRC8的计算过程, CRC校验算法,说白了,就是把需要校验的数据与多项式…

CCF CSP认证 历年题目自练Day34

题目一 试题编号&#xff1a; 202303-1 试题名称&#xff1a; 田地丈量 时间限制&#xff1a; 1.0s 内存限制&#xff1a; 512.0MB 问题描述&#xff1a; 问题描述 西西艾弗岛上散落着 n 块田地。每块田地可视为平面直角坐标系下的一块矩形区域&#xff0c;由左下角坐标 (x1,…

xml schema中的all元素

说明 xml schema中的all元素表示其中的子元素可以按照任何顺序出现&#xff0c;每个元素可以出现0次或者1次。 https://www.w3.org/TR/xmlschema-1/#element-all maxOccurs的默认值是1&#xff0c;minOccurs 的默认值是1。 举例 <element name"TradePriceRequest&…