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的数字&…...
mkcert 命令文档 - 本地 HTTPS 开发证书生成工具详解
1. 命令简介mkcert 是一个用 Go 语言编写的、零配置的本地开发用自签名证书生成工具。它能够自动创建并安装本地证书颁发机构(CA)到系统的信任存储中,并生成受本地信任的开发证书,大幅简化 HTTPS 本地开发环境的搭建过程ÿ…...
DeepSeek-OCR-2效果展示:OCR结果直接生成可编辑Word/PDF双格式
DeepSeek-OCR-2效果展示:OCR结果直接生成可编辑Word/PDF双格式 本文展示DeepSeek-OCR-2模型的强大OCR能力,重点演示如何将扫描文档直接转换为可编辑的Word和PDF格式,让文档数字化变得简单高效。 1. 核心能力概览 DeepSeek-OCR-2是2026年1月发…...
Python MCP服务部署卡在step3?揭秘92%开发者忽略的config.toml权限校验机制(配置失效终极诊断指南)
第一章:Python MCP服务部署卡在step3的典型现象与问题定位当执行 Python MCP(Model Control Platform)服务自动化部署脚本时,step3(即服务容器化构建与镜像推送阶段)常出现长时间无响应、日志停滞于 Buildi…...
GLM-Image技术验证:长宽比对构图影响实测数据
GLM-Image技术验证:长宽比对构图影响实测数据 1. 项目背景介绍 GLM-Image是由智谱AI开发的先进文本到图像生成模型,提供了一个美观易用的Web交互界面。这个界面基于Gradio构建,让用户能够轻松使用GLM-Image模型生成高质量的AI图像。 在实际…...
AI浪潮冲击下,前端该何去何从
🌊 初级前端工程师:向“深水区”扎根技能树与学习路径定位:面向初级前端开发工程师,聚焦底层原理、工程化思维与可验证的实战输出,构建 AI 时代不可替代的技术护城河。📐 核心原则(避坑指南&…...
3天掌握MediaPipe:从零开始构建实时AI应用的终极指南
3天掌握MediaPipe:从零开始构建实时AI应用的终极指南 【免费下载链接】mediapipe Cross-platform, customizable ML solutions for live and streaming media. 项目地址: https://gitcode.com/GitHub_Trending/med/mediapipe 想快速上手实时AI应用开发却不知…...
Phi-3-mini-4k-instruct-gguf应用案例:HR招聘话术生成、产品FAQ自动整理、日报模板填充
Phi-3-mini-4k-instruct-gguf应用案例:HR招聘话术生成、产品FAQ自动整理、日报模板填充 1. 模型简介 Phi-3-mini-4k-instruct-gguf是微软推出的轻量级文本生成模型,特别适合处理问答、文本改写和内容整理等任务。这个GGUF版本的模型经过优化࿰…...
弦音墨影保姆级教程:解决‘视频加载失败’‘墨迹不跟随目标’等10类高频问题
弦音墨影保姆级教程:解决‘视频加载失败’‘墨迹不跟随目标’等10类高频问题 1. 系统简介与核心价值 「弦音墨影」是一款将人工智能技术与传统美学完美融合的视频分析工具。它采用水墨丹青的视觉风格,通过先进的Qwen2.5-VL多模态技术,让视频…...
Kandinsky-5.0-I2V-Lite-5s镜像免配置优势:内置VAE/CLIP/Qwen2.5-VL,开箱即用
Kandinsky-5.0-I2V-Lite-5s镜像免配置优势:内置VAE/CLIP/Qwen2.5-VL,开箱即用 1. 产品概述 Kandinsky-5.0-I2V-Lite-5s是一款轻量级图生视频模型,专为快速视频创作设计。只需上传一张首帧图片,再补充一句运动或镜头描述…...
手把手教你部署M2FP:快速搭建人体部位识别服务
手把手教你部署M2FP:快速搭建人体部位识别服务 1. 引言:为什么选择M2FP进行人体解析? 在计算机视觉领域,人体解析(Human Parsing)是一项关键技术,它能够将图像中的人体划分为多个语义区域&…...

