Python - 深夜数据结构与算法之 Two-Ended BFS
目录
一.引言
二.双向 BFS 简介
1.双向遍历示例
2.搜索模版回顾
三.经典算法实战
1.Word-Ladder [127]
2.Min-Gen-Mutation [433]
四.总结
一.引言
DFS、BFS 是常见的初级搜索方式,为了提高搜索效率,衍生了剪枝、双向 BFS 以及 A* 即启发式搜索等高级搜索方式。剪枝通过避免不必要或者次优解来减少搜索的次数,提高搜索效率;双向 BFS 通过层序遍历从首尾逼近答案,提高搜索效率;启发式搜索则是从优先级的角度出发,基于优先级高低搜索,提高搜索效率。本文主要介绍双向 BFS 的使用。
二.双向 BFS 简介
1.双向遍历示例
◆ 双向连通图
求 A -> L 所需最短路径。

◆ 遍历层级关系
不同颜色代表不同层级的 BFS,绿色为 root,蓝色为第二层,从左向右递推。

◆ 双向遍历
从 A/L 同时层序遍历,当二者扩散的点重合时,左右路径长度相加即为最短路径。

2.搜索模版回顾
◆ DFS - 递归

◆ DFS - 非递归

◆ BFS - 栈

三.经典算法实战
1.Word-Ladder [127]
单词接龙: https://leetcode.cn/problems/word-ladder/description/

◆ 单向 BFS
class Solution: def ladderLength(self, beginWord, endWord, wordList):""":type beginWord: str:type endWord: str:type wordList: List[str]:rtype: int"""valid_word = set(wordList)if endWord not in valid_word:return 0stack = [(beginWord, 1)]while stack:word, level = stack.pop(0)for i in range(len(word)):for char in "abcdefghijklmnopqrstuvwxyz":new_word = word[:i] + char + word[i + 1:]if new_word == endWord:return level + 1elif new_word in valid_word:stack.append((new_word, level + 1))valid_word.remove(new_word)return 0
这里我们可以打印一下转换的流程图,hot 有多层 level 出发,第二条路径走到了 cog,即结束遍历,当然 log 也可以走到 cog 只不过已经不需要了。
hot 2 -> lot 3
hot 2 -> dot 3 -> dog 4 -> cog 5
hot 2 -> dot 3 -> log 4


◆ 双向 BFS
class Solution(object):def ladderLength(self, beginWord, endWord, wordList):""":type beginWord: str:type endWord: str:type wordList: List[str]:rtype: int"""# 去重使用valid_word = set(wordList)# 边界条件if endWord not in wordList or len(wordList) == 0:return 0# 双向 BFSbegin, end, step = {beginWord}, {endWord}, 1# 同时有元素才能继续,如果一遍没元素代表已中断,无法联通,直接结束while begin and end:# 减少排查的可能性,从单词少的方向排查,避免无效查询if len(begin) > len(end):begin, end = end, begin# 存储下一层next_level = set()# 遍历下一层的多个结果for word in begin:# 遍历每个位置for i in range(len(word)):# a-zfor char in "abcdefghijklmnopqrstuvwxyz":# 节省无必要的替换if char != word[i]:new_word = word[:i] + char + word[i + 1:]# 二者相遇即返回if new_word in end:return step + 1if new_word in valid_word:next_level.add(new_word)valid_word.remove(new_word)# 指针替换begin = next_levelstep += 1return 0
已经将详细的注释加在代码里了,从 {start},{end} 两个方向查找,每次只找短的缩小无效查询的次数,这其实也是一种剪枝的策略,正所谓图中有真意欲辨已忘言:


◆ 双向 BFS + 剪枝
class Solution(object):def ladderLength(self, beginWord, endWord, wordList):""":type beginWord: str:type endWord: str:type wordList: List[str]:rtype: int"""# 去重使用valid_word = set(wordList)if endWord not in wordList or len(wordList) == 0:return 0# 剪枝优化s = set()for word in wordList:for char in word:s.add(char)s = ''.join(list(s))# 双向 BFSbegin, end, step = {beginWord}, {endWord}, 1while begin and end:if len(begin) > len(end):begin, end = end, begin# 存储下一层next_level = set()for word in begin:for i in range(len(word)):# a-zfor char in s:# 节省无必要的替换if char != word[i]:new_word = word[:i] + char + word[i + 1:]if new_word in end:return step + 1if new_word in valid_word:next_level.add(new_word)valid_word.remove(new_word)# 指针替换begin = next_levelstep += 1return 0
上面的两个方法在构建 new_word 时都遍历了所有 26 个字母 char,其实我们可以根据 end_word 的去重字符进行状态空间压缩,从而减少无意义的遍历,因为 char not in end_word 则 new_word 必定 not in end_word,从而优化时间复杂度。

2.Min-Gen-Mutation [433]
最小基因突变: https://leetcode.cn/problems/minimum-genetic-mutation/description/

◆ BFS
class Solution(object):def minMutation(self, startGene, endGene, bank):""":type startGene: str:type endGene: str:type bank: List[str]:rtype: int"""if not bank:return -1bank = set(bank)if endGene not in bank:return -1stack = [(startGene, 0)]while stack:gene, level = stack.pop(0)for i in range(len(gene)):for char in "ACGT":new_gene = gene[:i] + char + gene[i + 1:]if new_gene == endGene:return level + 1if new_gene in bank:stack.append((new_gene, level + 1))bank.remove(new_gene)return -1
和上一题异曲同工之妙,只不过从单词接龙变成基因 🧬 接龙,每次修改的地方有限。

◆ 双向 BFS
class Solution(object):def minMutation(self, startGene, endGene, bank):""":type startGene: str:type endGene: str:type bank: List[str]:rtype: int"""if not bank:return -1bank = set(bank)if endGene not in bank:return -1# 初始化首尾front, back, step = {startGene}, {endGene}, 0while front and back:next_front = set()# 遍历当前层 Genefor gene in front:print(gene)for i in range(len(gene)):for char in "ACGT":new_gene = gene[:i] + char + gene[i + 1:]# 相遇了if new_gene in back:return step + 1# 下一层突变if new_gene in bank:next_front.add(new_gene)bank.remove(new_gene)# 取短的遍历加速if len(next_front) > len(back):front, back = back, next_frontelse:front = next_frontstep += 1return -1
和上面异曲同工,老曲新唱,相当于再温习一遍。其加速点就是左右替换,优先遍历可能性少的情况。

四.总结
这节内容 '双向 BFS' 起始也包含着很多剪枝的策略,所以其也属于优化搜索方式的方法之一,下一节我们介绍高级搜索的最后一块内容: A* 启发式搜索。
相关文章:
Python - 深夜数据结构与算法之 Two-Ended BFS
目录 一.引言 二.双向 BFS 简介 1.双向遍历示例 2.搜索模版回顾 三.经典算法实战 1.Word-Ladder [127] 2.Min-Gen-Mutation [433] 四.总结 一.引言 DFS、BFS 是常见的初级搜索方式,为了提高搜索效率,衍生了剪枝、双向 BFS 以及 A* 即启发式搜索…...
langchain-Agent-工具检索
有时会定义很多工具,而定义Agent的时候只想使用与问题相关的工具,这是可以通过向量数据库来检索相关的工具,传递给Agent # Define which tools the agent can use to answer user queries search SerpAPIWrapper() search_tool Tool(name …...
猫头虎分享:探索TypeScript的世界 — TS基础入门
博主猫头虎的技术世界 🌟 欢迎来到猫头虎的博客 — 探索技术的无限可能! 专栏链接: 🔗 精选专栏: 《面试题大全》 — 面试准备的宝典!《IDEA开发秘籍》 — 提升你的IDEA技能!《100天精通Golang》…...
Unity-生命周期函数
目录 生命周期函数是什么? 生命周期函数有哪些? Awake() OnEnable() Start() FixedUpdate() Update() Late Update() OnDisable() OnDestroy() Unity中生命周期函数支持继承多态吗? 生命周期函数是什么? 在Unity中&…...
SQL概述及SQL分类
SQL由IBM上世纪70年代开发出来,是使用关系模型的数据库应用型语言,与数据直接打交道。 SQL标准 SQL92,SQL99,他们分别代表了92年和99年颁布的SQL标准,我们今天使用的SQL语言依旧遵循这些标准。 SQL的分类 DDL:数据定…...
[VSCode] VSCode 常用快捷键
文章目录 VSCode 源代码编辑器VSCode 常用快捷键分类汇总01 编辑02 导航03 调试04 其他05 重构06 测试07 扩展08 选择09 搜索10 书签11 多光标12 代码片段13 其他 VSCode 源代码编辑器 官网:https://code.visualstudio.com/ 下载地址:https://code.visua…...
函数指针和回调函数 以及指针函数
函数指针(Function Pointer): 定义: 函数指针是指向函数的指针,它存储了函数的地址。函数的二制制代码存放在内存四区中的代码段,函数的地址它在内存中的开始地址。如果把函数的地址作为参数,就…...
京东年度数据报告-2023全年度游戏本十大热门品牌销量(销额)榜单
同笔记本市场类似,2023年度游戏本市场的整体销售也呈下滑态势。根据鲸参谋电商数据分析平台的相关数据显示,京东平台上游戏本的年度销量累计超过350万,同比下滑约6%;销售额将近270亿,同比下滑约11%。 鲸参谋综合了京东…...
秒懂百科,C++如此简单丨第十二天:ASCLL码
目录 必看信息 Everyday English 📝ASCLL码是什么? 📝ASCLL码表 📝利用ASCLL码实现大写转小写 📝小试牛刀 总结 必看信息 ▶本篇文章由爱编程的小芒果原创,未经许可,严禁转载。 ▶本篇文…...
Qt6入门教程 4:Qt Creator常用技巧
在上一篇Qt6入门教程 3:创建Hello World项目中,通过创建一个Qt项目,对Qt Creator已经有了比较直观的认识,本文将介绍它的一些常用技巧。 Qt Creator启动后默认显示欢迎页面 创建项目已经用过了,打开项目也很简单&#…...
阴盘奇门八字排盘马星位置计算方法php代码
如下位置,马星的四个位置。 计算方法: 1。先根据出生年月日,计算得八字四柱。比如 2024年01月09日,四柱为 其中时柱地支为“申” 2。然后根据以下对应的数组,来找到id号,即马星位置。 根据下表来找到&am…...
vue3 使用 jsoneditor
vue3 使用 jsoneditor 在main.js中引入 样式文件 import jsoneditor/dist/jsoneditor.css复制代码放到文件中就能用了 jsoneditor.vue <template><div ref"jsonDom" style"width: 100%; height: 460px"></div> </template> <…...
若依前后端分离版使用mybatis-plus实践教程
1、根目录得pom加入依赖 <properties><mybatis-plus.version>3.5.1</mybatis-plus.version> </properties> <dependencies><!-- mp配置--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus…...
SpringBoot-Dubbo-Zookeeper
Apache Dubbo:https://cn.dubbo.apache.org/zh-cn/overview/home/ 依赖 <!--dubbo--> <dependency><groupId>org.apache.dubbo</groupId><artifactId>dubbo-spring-boot-starter</artifactId><version>2.7.3</versio…...
华为HCIE课堂笔记第十二章 ICMPv6和NDP协议
第十二章 ICMPv6和NDP 12.1 背景 ICMPv6协议用于IPV6协议的消息传递:地址解析、重复地址检测、无状态地址配置、NDP协议、路径MTU发现。 12.2 ICMPv6介绍 ICMPv6的头部字段包含Type字段、Code字段、校验和字段。 消息分为两种: 查错消息ÿ…...
GNSS科研常用相关网站及资源
代码类: Github GitHub: Let’s build from here GitHub 导航相关开源项目 GNSS:RTKLIB、GAMP II-GOOD、GPSTest、GNSSLogger 组合导航:ignav、VINS、Multi_Sensor_Fusion Gitee(从Github导入后快速下载库) Gi…...
进程的创建与回收学习笔记
目录 一、进程内容: 二、进程常用命令 三、创建子进程 四、子进程进阶 五、进程的退出 六、进程的回收 一、进程内容: 程序: 存放在磁盘上的指令和数据的有序集合(文件) 静态的 进程: 执行一个程序所…...
【CCNet】《CCNet:Criss-Cross Attention for Semantic Segmentation》
ICCV-2019 文章目录 1 Background and Motivation2 Related Work3 Advantages / Contributions4 Method5 Experiments5.1 Datasets and Metrics5.2 Experiments on Cityscapess5.3 Experiments on ADE20K5.4 Experiments on COCO 6 Conclusion(own) 1 Ba…...
Qt QSQlite数据库插入字符串中存在单个双引号或单个单引号解决方案
1. 前言 当进行数据库写入或更新时,有时会遇到存在字符串中包含单个双引号或者单引号。 2. 单引号和双引号""作用 在数据库中,字符串常量时需要用一对英文单引号或英文双引号""将字符串常量括起来。 比如: select * …...
Linux系统中的IP地址、主机名、和域名解析
1.IP地址 每一台联网的电脑都会有一个地址,用于和其它计算机进行通讯 IP地址主要有2个版本,V4版本和V6版本(V6很少用,暂不涉及) IPv4版本的地址格式是:a.b.c.d,其中abcd表示0~255的数字&…...
<6>-MySQL表的增删查改
目录 一,create(创建表) 二,retrieve(查询表) 1,select列 2,where条件 三,update(更新表) 四,delete(删除表…...
简易版抽奖活动的设计技术方案
1.前言 本技术方案旨在设计一套完整且可靠的抽奖活动逻辑,确保抽奖活动能够公平、公正、公开地进行,同时满足高并发访问、数据安全存储与高效处理等需求,为用户提供流畅的抽奖体验,助力业务顺利开展。本方案将涵盖抽奖活动的整体架构设计、核心流程逻辑、关键功能实现以及…...
模型参数、模型存储精度、参数与显存
模型参数量衡量单位 M:百万(Million) B:十亿(Billion) 1 B 1000 M 1B 1000M 1B1000M 参数存储精度 模型参数是固定的,但是一个参数所表示多少字节不一定,需要看这个参数以什么…...
汽车生产虚拟实训中的技能提升与生产优化
在制造业蓬勃发展的大背景下,虚拟教学实训宛如一颗璀璨的新星,正发挥着不可或缺且日益凸显的关键作用,源源不断地为企业的稳健前行与创新发展注入磅礴强大的动力。就以汽车制造企业这一极具代表性的行业主体为例,汽车生产线上各类…...
服务器硬防的应用场景都有哪些?
服务器硬防是指一种通过硬件设备层面的安全措施来防御服务器系统受到网络攻击的方式,避免服务器受到各种恶意攻击和网络威胁,那么,服务器硬防通常都会应用在哪些场景当中呢? 硬防服务器中一般会配备入侵检测系统和预防系统&#x…...
python如何将word的doc另存为docx
将 DOCX 文件另存为 DOCX 格式(Python 实现) 在 Python 中,你可以使用 python-docx 库来操作 Word 文档。不过需要注意的是,.doc 是旧的 Word 格式,而 .docx 是新的基于 XML 的格式。python-docx 只能处理 .docx 格式…...
【Zephyr 系列 10】实战项目:打造一个蓝牙传感器终端 + 网关系统(完整架构与全栈实现)
🧠关键词:Zephyr、BLE、终端、网关、广播、连接、传感器、数据采集、低功耗、系统集成 📌目标读者:希望基于 Zephyr 构建 BLE 系统架构、实现终端与网关协作、具备产品交付能力的开发者 📊篇幅字数:约 5200 字 ✨ 项目总览 在物联网实际项目中,**“终端 + 网关”**是…...
【Java_EE】Spring MVC
目录 Spring Web MVC 编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 编辑参数重命名 RequestParam 编辑编辑传递集合 RequestParam 传递JSON数据 编辑RequestBody …...
汇编常见指令
汇编常见指令 一、数据传送指令 指令功能示例说明MOV数据传送MOV EAX, 10将立即数 10 送入 EAXMOV [EBX], EAX将 EAX 值存入 EBX 指向的内存LEA加载有效地址LEA EAX, [EBX4]将 EBX4 的地址存入 EAX(不访问内存)XCHG交换数据XCHG EAX, EBX交换 EAX 和 EB…...
.Net Framework 4/C# 关键字(非常用,持续更新...)
一、is 关键字 is 关键字用于检查对象是否于给定类型兼容,如果兼容将返回 true,如果不兼容则返回 false,在进行类型转换前,可以先使用 is 关键字判断对象是否与指定类型兼容,如果兼容才进行转换,这样的转换是安全的。 例如有:首先创建一个字符串对象,然后将字符串对象隐…...

