当前位置: 首页 > news >正文

经典图论算法回顾之Bellman-Ford算法

Dijkstra最短路径算法存在的一个问题是不能处理负权图(详见:经典图论算法回顾之Dijkstra算法。今天要回顾的Bellman-Ford算法(wikipedia:Bellman–Ford algorithm)可以求出有负权图的最短路径,并可以对最短路不存在(负权回路)的情况进行判断。

在这里插入图片描述

图1 Richard Ernest Bellman 1920-1984

一、Bellman-Ford算法

不同于Dijkstra算法基于点的操作,Bellman-Ford算法则是基于边的操作。算法的核心思想是不断地对边进行松弛(Relax)操作,松弛操作定义如下:

 RELAX(u, v, w)if v.d > u.d + w(u,v)v.d = u.d + w(u,v)v.p = u

上述代码用于松弛边 e = ( u , v ) e = (u,v) e=(u,v)。其中, v . d v.d v.d u . d u.d u.d 分别是 u , v u,v u,v 到源点的最短距离的估计值,这一值将会逐步逼近最短距离, w ( u , v ) w(u,v) w(u,v) 表示边的权重, v . p v.p v.p 表示 v v v 的潜在的最短路径上的前驱节点,用于得到最终得到某一节点的最短路径。

接下来,整个Bellman-Ford算法的伪代码如下:

 BELLMAN-FORD(G,w,s)INITIALIZE-SIGNAL-SOURCE(G,s)   //初始化操作:s.d = 0, (V-s).d = ∞for i=1 to |G.V|-1             //重复执行 |G.V| -1 次for each edge(u,v) in G.E  //对每条边进行松弛操作RELAX(u,v,w)for each edge(u,v) in G.E      //检测是否存在边还可以继续松弛if v.d > u.d + w(u,v)      //如果存在,则有负权回路,不存在最短路径return FALSEreturn TRUE

接下里给出一个简单的示例。如图1(a)所示,我们要求源点 S S S 到其它各点的最短路径,图1(b)是初始化结果(除了源点外,其他点到源点的最短距离初始化为无穷大)。

我们指定按 ( C , D ) , ( B , D ) , ( A , C ) , ( A , B ) , ( A , D ) , ( B , C ) , ( S , B ) , ( S , A ) (C,D),(B,D),(A,C),(A,B),(A,D),(B,C),(S,B),(S,A) (C,D),(B,D),(A,C),(A,B),(A,D),(B,C),(S,B),(S,A) 的顺序依次松弛各条边。
在这里插入图片描述

图1 (a) 给定的图,(b) 初始化结果

第1轮松弛:我们发现, ( C , D ) , ( B , D ) , ( A , C ) , ( A , B ) , ( A , D ) , ( B , C ) (C,D),(B,D),(A,C),(A,B),(A,D),(B,C) (C,D),(B,D),(A,C),(A,B),(A,D),(B,C) 均无法松弛,只有 ( S , B ) (S,B) (S,B) ( S , A ) (S,A) (S,A) 可以被松弛,松弛结果如下(圆圈中的数字表示到源点的最短路径距离的估值):

在这里插入图片描述

图2 第1轮松弛结果

第2轮松弛 ( C , D ) (C,D) (C,D) 无法被松弛,我们松弛 ( B , D ) , ( A , C ) , ( A , B ) (B,D),(A,C),(A,B) (B,D),(A,C),(A,B) ,松弛结果如下:

在这里插入图片描述

图3 第2轮松弛 (B,D),(A,C),(A,B) 的结果

我们接下来松弛 ( A , D ) , ( B , C ) (A,D),(B,C) (A,D),(B,C) ,松弛结果如下:
在这里插入图片描述

图4 第2轮松弛 (A,D),(B,C) 的结果

最后松弛 ( S , B ) , ( S , A ) (S,B),(S,A) (S,B),(S,A), 这两条边也无法继续松弛。

后续进行的第3轮和第4轮实际上已经无法急促松弛,最终得到的结果如下:
在这里插入图片描述

图5 最终结果(最短路径树)

二、算法正确性

为什么该算法可以得到正确的结果呢? 下面就不严谨地解释一下。

2.1 收敛性性质

收敛性如下图所示(图片来自于:https://zhuanlan.zhihu.com/p/585315996):
在这里插入图片描述
也就是说,如果某个点 v v v 的前驱节点 u u u 已经在最短路径上,那么, s ⇝ u → v s\rightsquigarrow u \rightarrow v suv 一定是 s s s v v v 的最短路径,且在之后这个最短路径值不再改变。

我们结合上图给出一个例子。这里我们将图1和图5搬下来,其中图5是图1的最短路径树。
在这里插入图片描述

图6 图1及其对应的最短路径树(粗边表示树边)

1)在第1轮松弛中,我们在松弛 ( S , B ) , ( S , A ) (S,B),(S,A) (S,B),(S,A) 时,得到了 d ( s , A ) = 2 d(s,A) = 2 d(s,A)=2, d ( s , B ) = 6 d(s,B) = 6 d(s,B)=6,可以发现 S → B S\rightarrow B SB 并未在最短路径树上, 因此可能在后续松弛过程中被更新。

2)在第2轮松弛中,我们在松弛 ( A , B ) (A,B) (A,B) 时,得到了 d ( s , B ) = 5 d(s,B) = 5 d(s,B)=5。此时, B . p = A B.p = A B.p=A B B B的前驱节点更新为 A A A)且 S → A S\rightarrow A SA 已经在最短路径上。为此,进一步确定 B B B的最短路径: S → A → B S\rightarrow A\rightarrow B SAB, 最短距离 d ( S , B ) = 5 d(S,B) = 5 d(S,B)=5 。在后续的松弛操作中, B B B 的最短路径将保持不变,因为没有更短的距离了。

在算法执行过程中,无论边的松弛顺序如何,在第1轮松弛过程中,总能确定最短路径树上跟源点 S S S 直接相连的点 U U U 的最短距离。在第2轮松弛过程中,总能确定那些跟 U U U 相邻点的最短距离。 依此类推,就可以得到所有点的最短距离。

2.2 为什么要执行 ∣ G . V ∣ − 1 |G.V|-1 G.V1轮松弛

如图1所示的图,我们仅执行2轮松弛操作就可以得到最终结果,那么为什么要执行 ∣ G . V ∣ − 1 |G.V|-1 G.V1轮松弛呢?

这是基于最坏情况考虑的,如果某个图如下所示,边的松弛顺序为 ( v n − 1 , v n ) , ⋯ ( v 2 , v 3 ) , ( v 1 , v 2 ) (v_{n-1},v_n), \cdots (v_2,v_3), (v_1,v_2) (vn1,vn),(v2,v3),(v1,v2), 那么每次只能从前往后确定一个顶点的最短路径,因此确实需要 ∣ G . V ∣ − 1 |G.V|-1 G.V1轮松弛操作才能得到最终结果。
在这里插入图片描述

图7 退化成单链的图

2.3 改进策略 SPFA

针对图7所示的单链表式的图,按照上述给定的边松弛顺序,我们确实需要 ∣ G . V ∣ − 1 |G.V|-1 G.V1轮松弛才能得到最终结果。 那么如何改进呢? 针对上图,我们如果从前往后更新似乎一轮松弛就可以得到最终结果。

为此,SPFA应运而生,该算法可以看做最短路径跟BFS的结合。它从源点开始,逐步向外松弛邻接点,相当于大体指定了边的松弛顺序,从而提升速度。

这里就不再讲述SPFA,具体可以参见:知乎:算法系列——四种最短路算法:Floyd,Dijkstra,Bellman-Ford,SPFA。

三、处理负权图

接下里,我们用图8来说明Bellman-Ford处理负权图的能力。

  • 对于上面的case,假设 v 0 v_0 v0 为源点, 给定边的松弛顺序为: ( v 3 , v 4 ) (v_3,v_4) (v3,v4), ( v 1 , v 3 ) (v_1,v_3) (v1,v3), ( v 1 , v 2 ) (v_1,v_2) (v1,v2), ( v 0 , v 1 ) (v_0,v_1) (v0,v1), ( v 0 , v 2 ) (v_0,v_2) (v0,v2)
    第1轮松弛后,可以得到: d ( v 0 , v 1 ) = 4 d(v_0,v_1) = 4 d(v0,v1)=4, d ( v 0 , v 2 ) = 2 d(v_0,v_2) = 2 d(v0,v2)=2
    在第2轮松弛过程中,当松弛 ( v 1 , v 2 ) (v_1,v_2) (v1,v2)时,可以得到: d ( v 0 , v 2 ) = 4 + ( − 3 ) = 1 d(v_0,v_2) = 4+(-3)=1 d(v0,v2)=4+(3)=1

  • 对于下面的case,无论以何种松弛顺序,在4轮松弛后, v 1 , v 2 , v 3 , v 4 v_1,v_2,v_3,v_4 v1,v2,v3,v4 到源点的最短路径均可更新。 为此,通过Bellman-Ford算法的最后一次环路检测就可以判别出来。

在这里插入图片描述

图8 Bellman-Ford算法可以处理负权图

四、结语

本文简单回顾了Bellman-Ford最短路径算法的思想,以及不严谨的说明了算法的正确性。鉴于作者水平,如有不正确之处,还请读者批评指正!

参考资料

[1] wikipedia:Bellman–Ford algorithm
[2] 知乎:图解Bellman-Ford计算过程以及正确性证明
[3] 知乎:算法系列——四种最短路算法:Floyd,Dijkstra,Bellman-Ford,SPFA

相关文章:

经典图论算法回顾之Bellman-Ford算法

Dijkstra最短路径算法存在的一个问题是不能处理负权图(详见:经典图论算法回顾之Dijkstra算法。今天要回顾的Bellman-Ford算法(wikipedia:Bellman–Ford algorithm)可以求出有负权图的最短路径,并可以对最短…...

LinuxC++(10):调用可执行程序

认识system函数 可以直接用system在代码中实现调用shell命令 /bin/ls -l /tmp表示执行ls -l命令,打开/tmp地址 而前面的/bin/表示这是shell命令,不可少,可以认为,/bin/后面的就是等价于shell里面输入的命令。 然后,cou…...

C语言指针·高级用法超详解(指针运算、野指针、悬空指针、void类型指针、二级以及多级指针)

目录 1. 指针的运算 2. 野指针和悬空指针 2.1 野指针 2.2 悬空指针 3. void类型指针 4. 二级指针和多级指针 4.1 命名规则 4.2 作用 4.2.1 二级指针可以操作一级指针记录的地址 4.2.2 利用二级指针获取变量中记录的数据 1. 指针的运算 文章开始前可以先了…...

SQL注入:MySQL元数据库,外网实战手工SQL注入

MySQL元数据库 MySQL的元数据库是一组特殊的数据库,用于存储MySQL服务器的元数据信息,在sql注入中较为常用为以下两种元数据库: information_schema:这个数据库包含了MySQL服务器上所有其他数据库的元数据信息。例如数据库名、表…...

接口与抽象类有什么区别

接口:只能包含抽象方法,成员变量只能是public static final 类型 是对行为的抽象 先约定再接口再实现 抽象类:包含成员变量和一般方法和抽象方法,当继承时,子类必须实现抽象类中的抽象方法...

【时时三省】unity test 测试框架 使用 code blocks 移植(核心文件:unity.c, unity_fixture.c)

山不在高,有仙则名。水不在深,有龙则灵。 ----CSDN 时时三省 目录 1,移植介绍 2,使用 Code::Blocks 17.12 创建工程 3,搬移文件入工程目录 4,更改代码 5,向工程添加文件 6,运…...

安装Docker以及安装过程中的错误解决

一、纯享版教程+操作截图 环境:centOs 7 FinalShell !!!此教程针对第一次安装docker的友友,如果已经安装过且报错的朋友,请移步报错合集。 1.卸载旧版本(无论是否安装过都建议执…...

PXE实验

实验前准备 关闭VMware的dhcp 点击 编辑 点击 虚拟网络编辑器 选择 NAT模式 将dhcp取消勾选 准备两台虚拟机 一台试验机,(网络环境正常并且有图形化的界面的rhel7) 一台测试机 init 5 --------------> 开启图形化界面 如…...

Spring - 解析 统一数据格式返回以及统一异常处理

接上篇文章的统一数据格式返回… 文章目录 1. 统一异常处理1.1 使用 2. 统一数据返回和统一异处理是怎么实现的2.1 initHandleAdapters2.2 initHandleExceptionResolvers 1. 统一异常处理 1.1 使用 统一异常处理的两个关键的注解是ControllerAdvice ExceptionHandler Contro…...

用Manim实现——计算和绘制图形下方区域

用Manim实现——计算和绘制图形下方区域 get_area 函数 get_area是一个用于计算和绘制图形下方区域的函数,常用于图形动画库(如 Manim) get_area(graph, x_rangeNone, color(ManimColor(#58C4DD),ManimColor(#83C167)), opacity0.3, bounde…...

MySQL 保姆级教程(十五): 组合查询

第 17 章 组合查询 17.1 组合查询 MySQL 允许执行多个查询(多条 SELECT 语句),并将结果作为单个查询集返回 17.2 创建组合查询 可用 UNION 操作符来组合数条 SQL 查询 17.2.1 使用 UNION 输入: SELECT user.USER FROM user UNION SELEC…...

《动手做科研》06. 如何产生新的研究想法

地址链接:《动手做科研》06. 如何产生新的研究想法 欢迎加入我的知识星球,定期分享AI论文干货知识! 导读: 提出好的研究想法是相当困难的,特别是当你刚接触一个领域时——这需要对文献中的空白有所了解。然而,产生研究想法的过程可…...

【Kubernetes】Deployment 的状态

Deployment 的状态 Deployment 控制器在整个生命周期中存在 3 3 3 种状态: 已完成(Complete)进行中(Progressing)失败(Failed) 通过观察 Deployment 的当前特征,可以判断 Deploym…...

新手学习Gazebo+ros仿真控制小车-----易错和自己理解

赵虚左老师讲的很详细,这里只是理一下思路,说下突然出现“新”概念之间的关系。 urdf文件:里面是配置模型的,既有模型的位置、尺寸、颜色,也包含复杂的物理模型信息比如:转动惯量,碰撞box大小等等&#xff…...

jdbc(mysql)

1.概述 jdbc:java database connection(java与数据库连接) java可以连接不同数据库,不同数据库连接细节不同,具体细节都由数据库自己实现 由java设计出一系列连接数据库的接口规范,然后由不同的数据库开发…...

【Linux】搜索log在哪个文件中执行的方法

在Linux中,如果你需要找到包含特定文本(比如一段log)的文件,你可以使用grep命令结合一些其他工具来实现这一目的。这里有几个方法可以帮助你找到包含特定log内容的文件。 1. 使用grep直接在特定目录或文件中搜索 如果你知道log大…...

web小游戏开发:2048(完)移动操作及动画效果

web小游戏开发:2048(完)移动操作及动画效果 添加随机数字游戏开始时的初始化显示分数移动和合并获取行列元素下标记录移动轨迹完整的 js小结添加随机数字 书接前文,我们在前边定义了一个 move 方法,暂时先往后放放。 在我们已经初始化好的界面上,我们需要先制作一个出现…...

Redis学习笔记——第20章 Lua脚本

第20章 Lua脚本 20.1 创建并修改Lua环境 20.1.1 创建Lua环境 服务器创建一个新的基本的Lua环境 20.1.2 载入函数库 修改Lua环境,载入一些库函数 20.1.3 创建redis全局表格 全局变量,支持在Lua脚本中执行redis命令 20.1.4 使用redis自制随机函数来…...

MySQL--日志管理

前言:本博客仅作记录学习使用,部分图片出自网络,如有侵犯您的权益,请联系删除 一、日志简介 MySQL日志主要分为4类,使用这些日志文件,可以查看MySQL内部发生的事情。这4类日志分别是: 错误日志&#xff1…...

【Nuxt】内置组件和全局样式使用

内置组件 Nuxt3框架也提供一些内置的组件,常用的如下: SEO组件:Html、Body、Head、Title、Meta、Style、Link、NoScript、BaseNuxtWelcome:欢迎页面组件,该组件是nuxt/ui的部分NuxtLayout:是Nuxt自带的页面布局组件NuxtPage:是N…...

基于大模型的 UI 自动化系统

基于大模型的 UI 自动化系统 下面是一个完整的 Python 系统,利用大模型实现智能 UI 自动化,结合计算机视觉和自然语言处理技术,实现"看屏操作"的能力。 系统架构设计 #mermaid-svg-2gn2GRvh5WCP2ktF {font-family:"trebuchet ms",verdana,arial,sans-…...

【Oracle APEX开发小技巧12】

有如下需求: 有一个问题反馈页面,要实现在apex页面展示能直观看到反馈时间超过7天未处理的数据,方便管理员及时处理反馈。 我的方法:直接将逻辑写在SQL中,这样可以直接在页面展示 完整代码: SELECTSF.FE…...

以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:

一、属性动画概述NETX 作用:实现组件通用属性的渐变过渡效果,提升用户体验。支持属性:width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项: 布局类属性(如宽高)变化时&#…...

YSYX学习记录(八)

C语言&#xff0c;练习0&#xff1a; 先创建一个文件夹&#xff0c;我用的是物理机&#xff1a; 安装build-essential 练习1&#xff1a; 我注释掉了 #include <stdio.h> 出现下面错误 在你的文本编辑器中打开ex1文件&#xff0c;随机修改或删除一部分&#xff0c;之后…...

【Redis技术进阶之路】「原理分析系列开篇」分析客户端和服务端网络诵信交互实现(服务端执行命令请求的过程 - 初始化服务器)

服务端执行命令请求的过程 【专栏简介】【技术大纲】【专栏目标】【目标人群】1. Redis爱好者与社区成员2. 后端开发和系统架构师3. 计算机专业的本科生及研究生 初始化服务器1. 初始化服务器状态结构初始化RedisServer变量 2. 加载相关系统配置和用户配置参数定制化配置参数案…...

对WWDC 2025 Keynote 内容的预测

借助我们以往对苹果公司发展路径的深入研究经验&#xff0c;以及大语言模型的分析能力&#xff0c;我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际&#xff0c;我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测&#xff0c;聊作存档。等到明…...

Python爬虫(二):爬虫完整流程

爬虫完整流程详解&#xff08;7大核心步骤实战技巧&#xff09; 一、爬虫完整工作流程 以下是爬虫开发的完整流程&#xff0c;我将结合具体技术点和实战经验展开说明&#xff1a; 1. 目标分析与前期准备 网站技术分析&#xff1a; 使用浏览器开发者工具&#xff08;F12&…...

unix/linux,sudo,其发展历程详细时间线、由来、历史背景

sudo 的诞生和演化,本身就是一部 Unix/Linux 系统管理哲学变迁的微缩史。来,让我们拨开时间的迷雾,一同探寻 sudo 那波澜壮阔(也颇为实用主义)的发展历程。 历史背景:su的时代与困境 ( 20 世纪 70 年代 - 80 年代初) 在 sudo 出现之前,Unix 系统管理员和需要特权操作的…...

WordPress插件:AI多语言写作与智能配图、免费AI模型、SEO文章生成

厌倦手动写WordPress文章&#xff1f;AI自动生成&#xff0c;效率提升10倍&#xff01; 支持多语言、自动配图、定时发布&#xff0c;让内容创作更轻松&#xff01; AI内容生成 → 不想每天写文章&#xff1f;AI一键生成高质量内容&#xff01;多语言支持 → 跨境电商必备&am…...

大模型多显卡多服务器并行计算方法与实践指南

一、分布式训练概述 大规模语言模型的训练通常需要分布式计算技术,以解决单机资源不足的问题。分布式训练主要分为两种模式: 数据并行:将数据分片到不同设备,每个设备拥有完整的模型副本 模型并行:将模型分割到不同设备,每个设备处理部分模型计算 现代大模型训练通常结合…...