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

双向广搜算法详解

双向广搜算法详解

    • 一、双向广搜基础概念
      • 1.1 算法核心思想
      • 1.2 适用场景
      • 1.3 与传统BFS的对比
    • 二、双向广搜的实现步骤
      • 2.1 初始化
      • 2.2 双向搜索循环
      • 2.3 路径还原(可选)
    • 三、Java代码实现(以单词接龙为例)
      • 代码说明
    • 四、双向广搜的优化技巧
      • 4.1 平衡两个方向的搜索进度
      • 4.2 高效的邻接节点生成
      • 4.3 路径还原的实现
    • 五、应用场景
      • 5.1 迷宫寻路
      • 5.2 数字变换问题
      • 5.3 社交网络最短路径
    • 六、局限性

常规广度优先搜索(BFS)中,当搜索空间较大时,算法可能需要遍历大量节点才能找到目标,效率较低,双向广搜(Bidirectional BFS)作为BFS的优化算法,通过从起点和终点同时开始搜索,在中间相遇时终止,能显著减少搜索的节点数量,大幅提升搜索效率。

一、双向广搜基础概念

1.1 算法核心思想

双向广搜算法的核心是“从两个方向同时搜索,在中间相遇时停止”。具体来说:

  • 从起点(正向搜索)和终点(反向搜索)分别启动BFS。
  • 每次搜索时,选择当前规模较小的搜索队列进行扩展(平衡两个方向的搜索进度)。
  • 当两个搜索方向在某个节点相遇(即该节点同时出现在两个搜索的已访问集合中),则找到一条从起点到终点的最短路径。
    双向广搜示意图

这种方式避免了传统BFS从单方向盲目扩展到远距离目标的问题,能有效减少搜索的节点总数(搜索规模从O(2^d)降至O(2^(d/2))d为最短路径长度)。

1.2 适用场景

双向广搜适用于以下场景:

  • 最短路径问题:如迷宫寻路、单词接龙、数字变换等需要找到最短路径的场景。
  • 状态空间较大:当单方向BFS需要遍历大量节点时,双向广搜的优势更明显。
  • 起点和终点明确:算法需要同时知道起点和终点,才能从两个方向启动搜索。

1.3 与传统BFS的对比

特性传统BFS双向广搜
搜索方向单方向(从起点到终点)双方向(起点和终点同时)
节点访问量较多(可能遍历远距离节点)较少(中间相遇时停止)
空间复杂度较高(需存储单方向所有节点)较低(两个方向的节点总和更少)
适用场景短路径或小状态空间长路径或大状态空间

二、双向广搜的实现步骤

2.1 初始化

  • 定义两个队列:正向队列(存储从起点扩展的节点)和反向队列(存储从终点扩展的节点)。
  • 定义两个已访问集合:正向访问集(记录正向搜索已遍历的节点)和反向访问集(记录反向搜索已遍历的节点)。
  • 将起点加入正向队列和正向访问集,将终点加入反向队列和反向访问集。
  • 初始化搜索步数(正向步数和反向步数)。

2.2 双向搜索循环

  • 若正向队列或反向队列为空,说明不存在路径,算法结束。
  • 选择当前规模较小的队列(平衡搜索进度),对其进行一层扩展:
    • 遍历队列中当前所有节点(按层扩展,保证步数准确)。
    • 对每个节点的邻接节点(下一步可能的状态)进行判断:
      • 若邻接节点在对方的访问集中(如正向扩展的节点出现在反向访问集中),则找到路径,返回总步数(正向步数+反向步数+1)。
      • 若邻接节点未在当前方向的访问集中,将其加入队列和访问集。
  • 增加对应方向的搜索步数,重复循环。

2.3 路径还原(可选)

若需要具体路径而非仅步数,可在访问集中记录每个节点的前驱(正向)或后继(反向),相遇后从相遇节点向前回溯至起点,向后回溯至终点,拼接得到完整路径。

三、Java代码实现(以单词接龙为例)

以下是基于双向广搜解决“单词接龙”问题的实现。问题描述:给定两个单词(beginWord和endWord)和一个单词列表,找到从beginWord到endWord的最短转换序列(每次转换只能改变一个字母,且转换后的单词必须在列表中)。

import java.util.*;public class BidirectionalBFS {// 单词接龙:双向广搜实现public int ladderLength(String beginWord, String endWord, List<String> wordList) {Set<String> wordSet = new HashSet<>(wordList);// 若终点不在单词列表中,直接返回0if (!wordSet.contains(endWord)) {return 0;}// 初始化正向和反向搜索Queue<String> forwardQueue = new LinkedList<>();Queue<String> backwardQueue = new LinkedList<>();Set<String> forwardVisited = new HashSet<>();Set<String> backwardVisited = new HashSet<>();forwardQueue.add(beginWord);forwardVisited.add(beginWord);backwardQueue.add(endWord);backwardVisited.add(endWord);int steps = 1; // 初始步数(起点和终点各算1步)// 双向BFS循环while (!forwardQueue.isEmpty() && !backwardQueue.isEmpty()) {// 选择规模较小的队列扩展,平衡搜索if (forwardQueue.size() > backwardQueue.size()) {// 交换队列和访问集,统一处理逻辑Queue<String> tempQueue = forwardQueue;forwardQueue = backwardQueue;backwardQueue = tempQueue;Set<String> tempVisited = forwardVisited;forwardVisited = backwardVisited;backwardVisited = tempVisited;}// 扩展当前层(正向队列)int size = forwardQueue.size();for (int i = 0; i < size; i++) {String current = forwardQueue.poll();// 生成所有可能的下一个单词List<String> neighbors = getNeighbors(current, wordSet);for (String neighbor : neighbors) {// 若在反向访问集中找到,说明相遇if (backwardVisited.contains(neighbor)) {return steps + 1;}// 若未访问过,加入队列和访问集if (!forwardVisited.contains(neighbor)) {forwardVisited.add(neighbor);forwardQueue.add(neighbor);}}}steps++;}// 未找到路径return 0;}// 生成当前单词的所有可能邻接单词(改变一个字母且在单词集中)private List<String> getNeighbors(String word, Set<String> wordSet) {List<String> neighbors = new ArrayList<>();char[] chars = word.toCharArray();// 遍历每个字符位置for (int i = 0; i < chars.length; i++) {char original = chars[i];// 尝试替换为a-z中其他字符for (char c = 'a'; c <= 'z'; c++) {if (c == original) {continue;}chars[i] = c;String newWord = new String(chars);if (wordSet.contains(newWord)) {neighbors.add(newWord);}}chars[i] = original; // 恢复原字符}return neighbors;}// 测试public static void main(String[] args) {BidirectionalBFS solver = new BidirectionalBFS();String beginWord = "hit";String endWord = "cog";List<String> wordList = Arrays.asList("hot", "dot", "dog", "lot", "log", "cog");int result = solver.ladderLength(beginWord, endWord, wordList);System.out.println("最短转换序列长度:" + result); // 输出5(hit->hot->dot->dog->cog)}
}

代码说明

  • 核心逻辑:通过正向和反向两个队列分别从起点和终点搜索,每次扩展规模较小的队列,相遇时返回总步数。
  • 邻接生成getNeighbors方法生成当前单词的所有可能变体(改变一个字母),并过滤出在单词集中的有效变体。
  • 平衡策略:通过交换队列保证每次扩展规模较小的方向,避免一个方向搜索过快导致的效率下降。

四、双向广搜的优化技巧

4.1 平衡两个方向的搜索进度

  • 选择小规模队列扩展:每次迭代时,优先扩展当前元素数量较少的队列(如正向队列有3个节点,反向队列有5个节点,则先扩展正向队列)。这种方式能避免一个方向过度扩展,平衡两个方向的搜索范围。
  • 限制单次扩展层数:每次对队列进行“一层扩展”(即遍历当前队列中的所有节点,生成下一层节点),保证两个方向的搜索按“步数”同步推进。

4.2 高效的邻接节点生成

邻接节点的生成效率直接影响算法性能。在单词接龙等问题中,可通过以下方式优化:

  • 预处理单词:将单词按“模式”分组(如“hot”可归为“ot”“ht”“ho*”),生成邻接单词时直接查询同模式的单词,避免逐个字符替换的冗余计算。
  • 剪枝无效节点:生成邻接节点时,直接过滤不在目标集合(如单词列表)中的节点,减少无效扩展。

4.3 路径还原的实现

若需要获取具体路径而非仅步数,可在访问集中存储“前驱”或“后继”关系:

  • 正向访问集存储节点->前驱节点(记录从起点到该节点的路径)。
  • 反向访问集存储节点->后继节点(记录从该节点到终点的路径)。
  • 相遇时,从相遇节点向前回溯至起点,向后回溯至终点,拼接得到完整路径。

五、应用场景

5.1 迷宫寻路

在二维迷宫中,从起点到终点的最短路径可通过双向广搜实现。正向搜索从起点扩展,反向搜索从终点扩展,相遇时得到最短路径,比传统BFS减少大量无效探索。

5.2 数字变换问题

如“将一个数字通过特定操作(如加减乘除、位运算)转换为另一个数字,求最少操作次数”。双向广搜从两个数字同时扩展,能快速找到中间转换节点。

5.3 社交网络最短路径

在社交网络中寻找两个用户的最短好友链(如“六度分隔理论”),双向广搜从两个用户同时搜索共同好友,效率远高于单方向BFS。

六、局限性

  • 需要明确终点:无法用于仅知道起点、终点未知的场景(如寻找所有可达节点)。
  • 实现较复杂:需要维护两个队列和访问集,处理相遇判断和路径拼接,逻辑比传统BFS复杂。
  • 不适用于非最短路径问题:若问题不需要最短路径(如任意路径),双向广搜的优势不明显。

总结
双向广搜通过从两个方向同时搜索,大幅减少了传统BFS的节点访问量,尤其在长路径、大状态空间的最短路径问题中表现优异。其核心优势在于“平衡搜索进度,在中间相遇时停止”,将搜索规模从指数级压缩至平方根级别。
实现双向广搜时,需注意以下关键点

  • 维护两个方向的队列和访问集,平衡搜索进度。
  • 高效生成邻接节点,减少无效扩展。
  • 相遇时准确计算路径长度,必要时还原具体路径。

That’s all, thanks for reading~~
觉得有用就点个赞、收进收藏夹吧!关注我,获取更多干货~

http://www.xdnf.cn/news/15795.html

相关文章:

  • 数据结构——单调栈
  • 服务管理智能化:R²AIN SUITE 升级带来的两大功能更新哪些值得关注?
  • SQLite / LiteDB 单文件数据库为何“清空表后仍占几 GB”?——原理解析与空间回收实战
  • 告别宕机!Ubuntu自动重启定时任务设置(一键脚本/手动操作)
  • 怎么自己搭建云手机
  • 数据库防止数组字符串序列化
  • 知识管理中的人工智能:概述、主要功能和管理工具
  • #vscode# #SSH远程# #Ubuntu 16.04# 远程ubuntu旧版Linux
  • 【Nginx】nginx+lua+redis实现限流
  • ARCS系统机器视觉实战(直播回放)
  • 医疗人工智能的心电图分析:创新技术与临床应用
  • Java面试宝典:Maven
  • 开源短链接工具 Sink 无需服务器 轻松部署到 Workers / Pages
  • nginx定制http头信息
  • 链表算法之【链表的中间节点】
  • 【Python】python 爬取某站视频批量下载
  • MyUI表单VcForm组件文档
  • Spring介绍以及IOC和AOP的实现
  • SpringBoot项目创建,三层架构,分成结构,IOC,DI相关,@Resource与@Autowired的区别
  • Camera相机人脸识别系列专题分析之十七:人脸特征检测FFD算法之libhci_face_camera_api.so 296点位人脸识别检测流程详解
  • Flutter——Android原生View是如何通过Flutter进行加载
  • 关于Mysql开启慢查询日志报错:13 - Permission denied的解决方案
  • logback日志控制服务器日志输出
  • 对Yii2中开启`authenticator`后出现的跨域问题-修复
  • 图机器学习(11)——链接预测
  • 现代R语言【Tidyverse、Tidymodel】的机器学习方法
  • Typecho博客集成阿里云CDN+OSS实现全站加速方案
  • 关于字符编辑器vi、vim版本的安装过程及其常用命令:
  • 第七章 愿景09 海波龙的坑
  • 数字化转型:概念性名词浅谈(第三十讲)