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

双向广搜算法详解

双向广搜算法详解

    • 一、双向广搜基础概念
      • 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.dtcms.com/a/285492.html

相关文章:

  • 【现有资料整理】灵枢 - 用于医学领域的 SOTA 多模态大语言模型
  • 对Yii2中开启`authenticator`后出现的跨域问题-修复
  • .QOI: Lossless Image Compression in O(n) Time
  • 变量命名规则
  • git--gitlab
  • 性能远超Spring Cloud Gateway!Apache ShenYu如何重新定义API网关!
  • 无标记点动捕:如何突破传统娱乐边界,打造沉浸式交互体验
  • 高速公路自动化安全监测主要内容
  • Elasticsearch+Logstash+Filebeat+Kibana部署(单机部署)
  • 在 Jenkins 中使用 SSH 部署密钥
  • JAVA高级第五章,简易超市会员管理系统
  • sqli-labs靶场通关笔记:第29-31关 HTTP参数污染
  • Android 应用保活思路
  • 小红书采集工具:无水印图片一键获取,同步采集笔记与评论
  • 银河麒麟高级服务器V10(ARM)安装人大金仓KingbaseES完整教程
  • 【unitrix】 6.7 基本结构体(types.rs)
  • IDEA插件离线安装
  • Vue3 Anime.js超级炫酷的网页动画库详解
  • 完整的 Meteor NPM 集成
  • 游戏常用运行库合集下载 - 提升游戏与软件体验
  • Nestjs框架: 基于TypeORM的多租户功能集成
  • Linux C 进程间通信基本操作
  • QT Windows 资源管理器的排序规则
  • 通俗易懂:什么是决策树?
  • 禁止拖动视频进度条来保障视频安全?
  • MBIST - Memory BIST会对memory进行清零吗?
  • QGIS二次开发环境搭建(qgis-3.28.6+qt5.15)
  • Telink BLE 低电压检测
  • AI IDE冲击下JetBrains作死,IDEA埋订阅陷阱
  • Redis中字符串类型的实现原理