算法训练营day60 图论⑩ Bellman_ford 队列优化算法、判断负权回路、单源有限最短路(修改后版本)

        增加对最短路径的优化算法、负权回路、单源有限最短的讲解

Bellman_ford 队列优化算法

--------------------------------------------------------------------------------

        8.24更新:该算法是针对带负值的最短路径的优化算法,核心通过队列来实现,代码中visit数组来标记在队列中的元素,以防重复入队,每次循环一个节点的出度,即自该节点出发的边,模拟过程中队列判断n次(均由队列实现,具体次数无循环语句要求)

--------------------------------------------------------------------------------

        我们之前的松弛算法,是对所有边进行松弛,真正有效的松弛,只有松弛 边(节点1->节点2) 和 边(节点1->节点3)。而松弛 边(节点4->节点6),边(节点5->节点3)等等 都是无效的操作,因为 节点4 和 节点 5 都是没有被计算过的节点。所以 Bellman_ford 算法 每次都是对所有边进行松弛,其实是多做了一些无用功。

        只需要对 上一次松弛的时候更新过的节点作为出发节点所连接的边 进行松弛就够了

        基于以上思路,如何记录 上次松弛的时候更新过的节点呢?用队列来记录。简单理解就是每次mindist数组中被更新过的位置就是需要入栈的元素,然后栈顶元素进行松弛操作出栈,同时被修改的元素入栈,最后完成所有的循环(其实用栈也行,对元素顺序没有要求)

效率分析

        如果图越稠密,则 SPFA的效率越接近与 Bellman_ford。反之,图越稀疏,SPFA的效率就越高。一般来说,SPFA 的时间复杂度为 O(K * N) K 为不定值,因为 节点需要计入几次队列取决于 图的稠密度。

        SPFA(队列优化版Bellman_ford) 在理论上 时间复杂度更胜一筹,但实际上,也要看图的稠密程度,如果 图很大且非常稠密的情况下,虽然 SPFA的时间复杂度接近Bellman_ford,但实际时间消耗 可能是 SPFA耗时更多。

代码实现

        最根本的理解,我们这个代码是以边为主的,注意本题没有 负权回路 。

import collectionsdef main():n, m = map(int, input().strip().split())edges = [[] for _ in range(n + 1)]for _ in range(m):src, dest, weight = map(int, input().strip().split())edges[src].append([dest, weight])minDist = [float("inf")] * (n + 1)minDist[1] = 0que = collections.deque([1])visited = [False] * (n + 1)visited[1] = Truewhile que:cur = que.popleft()visited[cur] = Falsefor dest, weight in edges[cur]:if minDist[cur] != float("inf") and minDist[cur] + weight < minDist[dest]:minDist[dest] = minDist[cur] + weightif visited[dest] == False:que.append(dest)visited[dest] = Trueif minDist[-1] == float("inf"):return "unconnected"return minDist[-1]if __name__ == "__main__":print(main())

Bellman_ford 判断负权回路

--------------------------------------------------------------------------------

        8.24更新:核心思路就是多松弛一次,看mindist数组有没有变化(负权回路会导致mindist数组变化),优化算法中的判断负权回路:如果节点加入队列的次数 超过了 n-1次 ,那么该图就一定有负权回路。

        怎么判断数组有没有变化?还是一样的逻辑,看有没有变小即可

        最后一点,普通方法把边都遍历一遍这种,没有什么特别的说法,直接多松弛一次即可,但是优化算法中的负权回路,因为引入了队列,所以一定记得对于队列内已经进入的元素不要重复进入,(这个是上一题目中的基本内容了),这里的拓展我认为有一些主动写错来纠偏的部分了,反而可能更加容易引起误导

--------------------------------------------------------------------------------

        本题是要我们判断 负权回路,也就是图中出现环且环上的边总权值为负数。如果在这样的图中求最短路的话, 就会在这个环里无限循环 (也是负数+负数 只会越来越小),无法求出最短路径。

        在上期博客中,bellman_ford 算法的核心就是一句话:对 所有边 进行 n-1 次松弛。 在没有负权回路的图中,松弛 n 次以上 ,结果不会有变化。但本题有 负权回路,如果松弛 n 次,结果就会有变化了,因为 有负权回路 就是可以无限最短路径(一直绕圈,就可以一直得到无限小的最短距离)。

        那么解决本题的 核心思路,就是在 n-1 次松弛 的基础上,再多松弛一次,看minDist数组 是否发生变化。

Bellman-Ford方法

import sysdef main():input = sys.stdin.readdata = input().split()index = 0n = int(data[index])index += 1m = int(data[index])index += 1grid = []for i in range(m):p1 = int(data[index])index += 1p2 = int(data[index])index += 1val = int(data[index])index += 1# p1 指向 p2,权值为 valgrid.append([p1, p2, val])start = 1  # 起点end = n    # 终点minDist = [float('inf')] * (n + 1)minDist[start] = 0flag = Falsefor i in range(1, n + 1):  # 这里我们松弛n次,最后一次判断负权回路for side in grid:from_node = side[0]to = side[1]price = side[2]if i < n:if minDist[from_node] != float('inf') and minDist[to] > minDist[from_node] + price:minDist[to] = minDist[from_node] + priceelse:  # 多加一次松弛判断负权回路if minDist[from_node] != float('inf') and minDist[to] > minDist[from_node] + price:flag = Trueif flag:print("circle")elif minDist[end] == float('inf'):print("unconnected")else:print(minDist[end])if __name__ == "__main__":main()

SPFA方法

        这里涉及的问题是节点重复入队,导致本应n-1次结束的过程,没有按时结束,我们需要记录哪些节点已经出队列了,哪些节点在队列里面,对于还在队列内部的节点不要重复入队

from collections import deque
from math import infdef main():n, m = [int(i) for i in input().split()]graph = [[] for _ in range(n+1)]min_dist = [inf for _ in range(n+1)]count = [0 for _ in range(n+1)]  # 记录节点加入队列的次数for _ in range(m):s, t, v = [int(i) for i in input().split()]graph[s].append([t, v])min_dist[1] = 0  # 初始化count[1] = 1d = deque([1])flag = Falsewhile d:  # 主循环cur_node = d.popleft()for next_node, val in graph[cur_node]:if min_dist[next_node] > min_dist[cur_node] + val:min_dist[next_node] = min_dist[cur_node] + valcount[next_node] += 1if next_node not in d: # 二刷的时候再好好理解d.append(next_node)if count[next_node] == n:  # 如果某个点松弛了n次,说明有负回路flag = Trueif flag:breakif flag:print("circle")else:if min_dist[-1] == inf:print("unconnected")else:print(min_dist[-1])if __name__ == "__main__":main()

Bellman_ford 单源有限最短路

--------------------------------------------------------------------------------

        8.24更新:节点数量为n,起点到终点,最多是 n-1 条边相连。 那么对所有边松弛 n-1 次 就一定能得到 起点到达 终点的最短距离。这个n - 1要理解,这也是松弛的最基本逻辑,就不多讲了。(如果对以上讲解看不懂,建议详看 kama94.城市间货物运输I )

        但是仅有这个理解是不够的,这里我还是要吐槽一下,可能是我没看视频,我认为这道题目的文字版解析说的不好,可能是我没有理解到位,我认为出现问题的本质是:在修改过程中修改了已经遍历过的节点的值,地基变了,自然下一轮中所有的值都会修改,引起这个问题的本质是:路径中存在负权回路,而不是文字版讲解中说的:基于了本次松弛的 minDist数值,而不是上一次 松弛时候minDist的数值。这个讲解歪曲了这个问题的本质,反而让全文显得很啰嗦并且具有误导性。

        解决这个问题的方法是:解决负权环路的判断,如何解决?环路最少有两条边,隔开这两条边,也就是文字版解析中给出的解决方法,但是这个是后验的,不可以在开始就把问题归结到这一点上

        最后dijkstra显然是不能用的

--------------------------------------------------------------------------------

        注意题目中描述是 最多经过 k 个城市的条件下,而不是一定经过k个城市,也可以经过的城市数量比k小,但要最短的路径。对于部分路径节点多但是距离短的结果增加了要求

        在 这个系列的第一个题目 中我们讲了:对所有边松弛一次,相当于计算 起点到达 与起点一条边相连的节点 的最短距离。节点数量为n,起点到终点,最多是 n-1 条边相连。 那么对所有边松弛 n-1 次 就一定能得到 起点到达 终点的最短距离。本题是最多经过 k 个城市, 那么是 k + 1条边相连的节点。 

Bellman-Ford方法求解单源有限最短路

def main():# 輸入n, m = map(int, input().split())edges = list()for _ in range(m):edges.append(list(map(int, input().split() )))start, end, k = map(int, input().split())min_dist = [float('inf') for _ in range(n + 1)]min_dist[start] = 0# 只能經過k個城市,所以從起始點到中間有(k + 1)個邊連接# 需要鬆弛(k + 1)次for _ in range(k + 1):update = Falsemin_dist_copy = min_dist.copy()for src, desc, w in edges:if (min_dist_copy[src] != float('inf') and min_dist_copy[src] + w < min_dist[desc]):min_dist[desc] = min_dist_copy[src] + wupdate = Trueif not update:break# 輸出if min_dist[end] == float('inf'):print('unreachable')else:print(min_dist[end])if __name__ == "__main__":main()

SPFA方法求解单源有限最短路

from collections import deque
from math import infdef main():n, m = [int(i) for i in input().split()]graph = [[] for _ in range(n+1)]for _ in range(m):v1, v2, val = [int(i) for i in input().split()]graph[v1].append([v2, val])src, dst, k = [int(i) for i in input().split()]min_dist = [inf for _ in range(n+1)]min_dist[src] = 0  # 初始化起点的距离que = deque([src])while k != -1 and que:visited = [False for _ in range(n+1)]  # 用于保证每次松弛时一个节点最多加入队列一次que_size = len(que)temp_dist = min_dist.copy()  # 用于记录上一次遍历的结果for _ in range(que_size):cur_node = que.popleft()for next_node, val in graph[cur_node]:if min_dist[next_node] > temp_dist[cur_node] + val:min_dist[next_node] = temp_dist[cur_node] + valif not visited[next_node]:que.append(next_node)visited[next_node] = Truek -= 1if min_dist[dst] == inf:print("unreachable")else:print(min_dist[dst])if __name__ == "__main__":main()

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

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

相关文章

Python 学习(十六) 下一代 Python 包管理工具:UV

目录1. UV 介绍1.1 什么是UV&#xff1f;1.2 UV的核心优势1.3 UV和其他工具对比1&#xff09;UV vs. pipvirtualenv2&#xff09;UV vs. Conda3&#xff09;UV vs. Poetry4&#xff09;功能对比表2. UV的安装与常用命令2.1 安装UV1&#xff09;使用官方安装脚本&#xff08;推荐…

Redis学习笔记 ----- 缓存

一、什么是缓存 缓存&#xff08;Cache&#xff09;是数据交换的缓冲区&#xff0c;是存储数据的临时地方&#xff0c;一般读写性能较高。 &#xff08;一&#xff09;缓存的作用 降低后端负载&#xff1a;减少对数据库等后端存储的直接访问压力。提高读写效率&#xff0c;降低…

React响应式链路

文章目录响应式链路的核心环节1.状态定义与初始化2.状态更新触发&#xff08;状态变更&#xff09;3.调度更新&#xff08;Scheduler&#xff09;4.重新渲染&#xff08;Render 阶段&#xff09;5.协调&#xff08;Reconciliation&#xff09;与 Fiber 架构6.提交更新&#xff…

软件设计师——计算机网络学习笔记

一、计算机网络 网络基础 1. 计算机网络的分类2. 网络拓扑结构 总线型(利用率低、干扰大、价格低) 星型(交换机形成的局域网、中央单元负荷大) 环型(流动方向固定、效率低扩充难) 树型(总线型的扩充、分级结构) 分布式(任意节点连接、管理难成本高)一般来说&#xff0c;办公室局…

1200 SCL学习笔记

一. IF. 如果。下面是一个起保停IF #I_start AND NOT #I_stop THEN //如果I_start接通 和 I_stop没有接通#Q_run : 1; //输出Q_run 接通 ELSIF #I_stop THEN //如果I_stop接通#Q_run : 0; //。。。。。。 END_IF;二. CASECASE…

单例模式与线程池

1. 单例模式单例模式是一种常用的设计模式&#xff0c;它确保一个类只有一个实例&#xff0c;并提供一个全局访问点来获取这个实例。这种模式在需要控制资源访问、管理共享状态或协调系统行为时非常有用。单例模式的核心特点&#xff1a;私有构造函数&#xff1a;防止外部通过n…

Chrome和Edge如何开启暗黑模式

Edge和Chrome浏览器都提供了实验性功能&#xff0c;可以通过修改实验性设置来开启暗黑模式。 在浏览器地址栏中输入edge://flags/&#xff08;Edge&#xff09;或chrome://flags/&#xff08;Chrome&#xff09;。在搜索框中输入“dark”&#xff0c;找到与暗黑模式相关的选项。…

【科研绘图系列】浮游植物的溶解性有机碳与初级生产力的关系

禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍 数据准备 数据处理 溶解性有机碳(DOC)与初级生产力(NPP)的关系 溶解性有机碳(DOC)与光照强度(PAR)的关系 数据可视化 加载R包 数据下载 导入数据 画图1 画图2 总结 系统信…

IDEA相关的设置和技巧

IDEA相关的设置和技巧 我的博客对应文章地址 1.布局设置 IDEA的布局自定义程度很高&#xff0c;顶部工具栏&#xff0c;侧边栏都可以随意定制&#xff0c;设置好的布局方案可以保存&#xff0c;在新项目中快速使用 1.1 工具栏设置 [!tip] 举个例子&#xff1a;比如我要在顶部…

AWS Lambda 完全指南:解锁无服务器架构的强大力量

在云计算的发展浪潮中,无服务器(Serverless) 架构已然成为构建现代应用的新范式。而在这场变革的中心,AWS Lambda 作为开创性的 Function-as-a-Service (FaaS) 服务,彻底改变了我们部署和运行代码的方式。 本文将带您深入探索 AWS Lambda,从核心概念、工作原理到高级实践…

人工智能时代下普遍基本收入(UBI)试验的实践与探索——以美国硅谷试点为例

一、硅谷UBI试验的最新进展&#xff08;2025年&#xff09;1. 试验规模与资金来源圣克拉拉县试点&#xff1a;硅谷所在地圣克拉拉县针对脱离寄养家庭的年轻人开展UBI试验&#xff0c;每月发放1000美元补贴&#xff0c;持续1-2年&#xff0c;覆盖约60名参与者&#xff0c;成本约…

云计算之云主机Linux是什么?有何配置?如何选?

一、云环境如何选择Linux发行版 1.1、Linux在各个领域的发展 Linux在各个领域的发展序号Linux发展领域说明1Linux在服务器领域的发展目前Linux在服务器领域已经占据95%的市场份额&#xff0c;同时Linux在服务器市场的迅速崛起&#xff0c;已经引起全球IT产业的高度关注&#xf…

XCVU13P-2FHGB2104E Xilinx(AMD)Virtex UltraScale+ FPGA

XCVU13P-2FHGB2104E 是 Xilinx&#xff08;AMD&#xff09;Virtex UltraScale FPGA 系列中的一款高性能芯片&#xff0c;适用于需要大量逻辑资源、高带宽和高速数据传输的应用场景。作为该系列中的旗舰产品&#xff0c;XCVU13P-2FHGB2104I 结合了强大的处理能力和灵活的可编程性…

自动化单词例句获取系统设计方案

方案一 (网络爬虫) 这个方案的核心思路是:创建一个自动化的脚本,该脚本会读取你 MongoDB 中的单词,然后去一个免费的在线词典网站上抓取这些单词的例句,最后将抓取到的例句存回你的 MongoDB 数据库中对应的单词条目下。 一、 核心思路与技术选型 自动化脚本: 我们将使用 P…

WPF Alert弹框控件 - 完全使用指南

WPF Alert弹框控件 - 完全使用指南概述快速开始nuget安装与引用基本用法功能特性详细说明AlertType 枚举方法参数详解Show 方法&#xff08;局部弹窗&#xff09;ShowGlobal 方法&#xff08;全局弹窗&#xff09;完整示例代码XAML 布局C# 代码实现界面演示功能特性对比表格自定…

可视化-模块1-HTML-01

1-软件下载&#xff1a; 软件名称&#xff1a;HBuilderX 官网地址&#xff1a; https://www.dcloud.io/hbuilderx.html 下载文佳-解压缩-打开exe文件 创建快捷方式至桌面 2-创建项目 【普通项目】-【基本HTML项目】-【项目名&#xff1a;week1-1】 【index】输入&#xff1…

机器翻译 (Machine Translation) 经典面试笔试50题(包括详细答案)

更多内容请见: 机器翻译修炼-专栏介绍和目录 文章目录 第一部分:基础理论与概念 (1-15题) 1. 题目: 什么是机器翻译(MT)?请简述其发展历程中的几个主要范式。 2. 题目: 机器翻译的主要评价指标有哪些?请详细解释BLEU指标的计算原理和优缺点。 3. 题目: 什么是平行语料…

linux中文本文件操作之grep命令

文章目录背景案例demo环境方式一、安装wsl方式二、安装grep一、查找指定字符串二、忽略大小写查找三、查找时显示行号四、统计匹配的次数五、精准匹配一个单词六、显示匹配上下文七、只显示匹配的内容八、按固定字符串匹配背景 在日常运维中会对日志文件&#xff0c;使用grep命…

链表漫游指南:C++ 指针操作的艺术与实践

文章目录0. 前言1. 链表的分类2. 单链表的实现2.1 链表的基本结构——节点&#xff08;Node&#xff09;2.2 核心操作详解2.2.1 构造和析构2.2.2 插入操作2.2.3 删除操作2.3.4 其他操作2.4 总结3. 双向链表的实现3.1 基本结构设计3.2 基本操作3.2.1 初始化与销毁3.2.2 插入与删…

Claude Code赋能企业级开发:外卖平台核心系统的智能化重构

开篇&#xff1a;万亿市场背后的技术挑战中国外卖市场日订单量超过1亿单&#xff0c;每一单背后都是一个复杂的技术链条&#xff1a;用户下单→商家接单→骑手抢单→实时配送→评价反馈。构建这样一个支撑千万级并发、涉及地理位置计算、实时调度、支付结算的超级平台&#xff…