BFS 和 DFS 编程思想、框架、技巧及经典例题总结

一、核心编程思想

  1. BFS(广度优先搜索)
  • 核心思想:以「层次遍历」为核心,从起点出发,优先探索距离起点最近的节点,逐层扩散遍历。
  • 本质:通过「队列」实现 “先入先出” 的顺序,保证每次处理的都是当前层的所有节点,适合解决「最短路径」「层次遍历」「连通性判断(无权图)」等问题。
  • 特点:
    • 遍历过程中节点的 “距离”(步数)是递增的,首次到达目标节点时的路径即为最短路径(无权图 / 等权图)。
    • 空间复杂度较高(需存储当前层所有节点),但时间复杂度稳定。
  1. DFS(深度优先搜索)
  • 核心思想:以「深度优先」为核心,从起点出发,沿着一条路径深入到底,直到无法前进时回溯,换另一条路径继续探索。
  • 本质:通过「递归栈」或「手动栈」实现 “后入先出” 的顺序,适合解决「连通性判断」「排列组合」「路径搜索」「回溯剪枝」等问题。
  • 特点:
    • 遍历过程具有 “回溯” 特性,可通过剪枝优化无效路径,减少计算量。
    • 空间复杂度由递归深度(或栈深度)决定,可能因深度过大导致栈溢出(需注意递归边界)。

二、通用编程框架

  1. BFS 框架(模板)
    基础模板(树 / 图通用)
    def bfs(start, target):# 初始化队列(存储待处理节点)from collections import dequequeue = deque([start])# 初始化visited(避免重复访问,图必备;树可省略,但需处理父节点回退)visited = set([start])# 记录距离/层数(可选,按需添加)distance = 0  while queue:# 处理当前层的所有节点(关键:先获取当前层节点数)level_size = len(queue)for _ in range(level_size):# 弹出队首节点current = queue.popleft()# 若找到目标,返回结果if current == target:return distance  # 或其他结果# 遍历当前节点的所有相邻节点for neighbor in get_neighbors(current):if neighbor not in visited:visited.add(neighbor)queue.append(neighbor)# 层数/距离+1(每处理完一层递增)distance += 1# 未找到目标return -1
    
    • 关键组件:
      • 队列:存储待处理的节点,保证 “先入先出”。
      • visited 集合:标记已访问节点(图中必用,避免环导致的死循环;树中可省略,但需通过父节点指针避免回退)。
      • 层次处理:通过level_size控制每层节点的批量处理,实现 “层次遍历”。
  2. DFS 框架(模板)
    递归版(简洁,适合深度不太大的场景)
    def dfs(current, visited, target):# 终止条件:找到目标或越界if current == target:return True  # 或记录路径if current is invalid: # 剪枝return False# 标记当前节点为已访问visited.add(current)# 遍历所有相邻节点for neighbor in get_neighbors(current):if neighbor not in visited:# 递归探索下一层if dfs(neighbor, visited, target):return True  # 找到目标,提前返回# 回溯:若需恢复状态(如排列组合问题),此处可移除标记# visited.remove(current)  # 视问题而定是否需要return False  # 未找到目标
    
    迭代版(手动栈,适合深度较大的场景,避免递归栈溢出)
    def dfs_iterative(start, target):stack = [start]  # 手动栈visited = set([start])while stack:current = stack.pop()  # 弹出栈顶节点(最后入栈的节点)# 找到目标,返回结果if current == target:return True# 遍历相邻节点(注意:栈是后入先出,若需按顺序遍历,可反向添加)for neighbor in reversed(get_neighbors(current)):if neighbor not in visited:visited.add(neighbor)stack.append(neighbor)return False
    
    • 关键组件:
      • 栈:递归时隐式使用函数调用栈,迭代时手动维护栈,保证 “后入先出”。
      • visited 集合:同 BFS,避免重复访问。
      • 回溯:在排列组合等问题中,需在递归返回后移除visited标记,允许节点被其他路径重新访问。

三、实用技巧

  1. BFS 技巧
  • 双向 BFS 优化:当起点和终点明确时(如最短路径问题),可从起点和终点同时开始 BFS,相遇时终止,大幅减少搜索空间(适用于节点数量庞大的场景,如「打开转盘锁」)。
  • 距离记录:通过数组或哈希表记录每个节点到起点的距离,无需额外变量(如distance[node] = distance[current] + 1)。
  • 层次信息利用:在层次遍历问题中(如二叉树每层节点之和),通过level_size区分不同层,方便按层处理数据。
  1. DFS 技巧
  • 剪枝优化:在递归过程中提前判断无效路径(如 “和超过目标的组合”“已存在重复的排列”),直接返回,减少计算量。
  • 记忆化搜索:对重复子问题(如动态规划中的递归场景),用哈希表记录已计算的结果,避免重复计算(如「斐波那契数列」「最长递增子序列」的递归实现)。
  • 路径跟踪:在需要记录具体路径的问题中(如「所有可能的路径」),通过列表在递归时添加节点,回溯时移除节点,动态维护路径。

四、经典例题解析

  1. BFS 经典例题
    例题 1:二叉树的层序遍历(LeetCode 102)
    • 问题:给定二叉树的根节点,返回按层序遍历的节点值(逐层从左到右)。
    • 思路:用 BFS 按层处理节点,每层遍历前记录队列长度(当前层节点数),依次弹出并收集值,再将子节点入队。
    • 代码:
    def levelOrder(root):if not root:return []from collections import dequequeue = deque([root])result = []while queue:level_size = len(queue)current_level = []for _ in range(level_size):node = queue.popleft()current_level.append(node.val)if node.left:queue.append(node.left)if node.right:queue.append(node.right)result.append(current_level)return result
    
    例题 2:打开转盘锁(LeetCode 752)
    • 问题:转盘锁有 4 个数字(0-9),每次可转动一个数字 ±1(如 0→9 或 0→1)。给定死亡数字列表(不可经过),求从 “0000” 到目标数字的最少步数,无法到达则返回 - 1。
    • 思路:BFS 求最短路径,每次转动生成 8 种可能的下一个状态(4 个位置,每个 ±1),用 visited 和死亡列表过滤无效状态,- 双向 BFS 可优化效率。
    • 核心代码:
    def openLock(deadends, target):from collections import dequedead = set(deadends)start = "0000"if start in dead:return -1if start == target:return 0# BFS初始化queue = deque([start])visited = set([start])steps = 0while queue:steps += 1level_size = len(queue)for _ in range(level_size):current = queue.popleft()# 生成所有可能的下一个状态for i in range(4):for d in (-1, 1):# 转动第i位数字(0-9循环)new_digit = (int(current[i]) + d) % 10next_str = current[:i] + str(new_digit) + current[i+1:]if next_str == target:return stepsif next_str not in visited and next_str not in dead:visited.add(next_str)queue.append(next_str)return -1
    
  2. DFS 经典例题
    例题 1:岛屿数量(LeetCode 200)
    • 问题:给定二维网格,‘1’ 表示陆地,‘0’ 表示水,求岛屿数量(相邻陆地相连,上下左右为相邻)。
    • 思路:遍历网格,遇到未访问的 ‘1’ 时启动 DFS,将所有相连的 ‘1’ 标记为已访问(避免重复计数),每启动一次 DFS 即增加一个岛屿。也可以使用BFS。
    • 代码:
    def numIslands(grid):if not grid:return 0rows, cols = len(grid), len(grid[0])visited = [[False for _ in range(cols)] for _ in range(rows)]count = 0def dfs(i, j):# 越界或非陆地或已访问,直接返回if i < 0 or i >= rows or j < 0 or j >= cols or grid[i][j] == '0' or visited[i][j]:returnvisited[i][j] = True  # 标记为已访问# 递归探索上下左右dfs(i+1, j)dfs(i-1, j)dfs(i, j+1)dfs(i, j-1)for i in range(rows):for j in range(cols):if grid[i][j] == '1' and not visited[i][j]:count += 1dfs(i, j)  # 标记整个岛屿return count
    
    例题 2:全排列(LeetCode 46)
    • 问题:给定不含重复数字的数组,返回所有可能的全排列。
    • 思路:DFS 回溯法,通过递归探索每个位置的可能数字,用 visited 标记已使用的数字,回溯时恢复状态以尝试其他组合。
    • 代码:
    def permute(nums):result = []n = len(nums)def backtrack(path, visited):# 终止条件:路径长度等于数组长度if len(path) == n:result.append(path.copy())returnfor i in range(n):if not visited[i]:# 选择当前数字visited[i] = Truepath.append(nums[i])# 递归下一层backtrack(path, visited)# 回溯:撤销选择path.pop()visited[i] = Falsebacktrack([], [False]*n)return result
    

五、BFS 与 DFS 的区别与适用场景

维度BFSDFS
数据结构队列(先入先出)栈 / 递归栈(后入先出)
适用场景最短路径(无权图)、层次遍历、拓扑排序排列组合、连通性、回溯剪枝、路径搜索
空间复杂度较高(取决于最宽层节点数)较高(取决于递归深度)
特点首次到达目标即最短路径可通过回溯剪枝优化效率

通过掌握上述框架和技巧,可快速解决大部分 BFS/DFS 相关问题。核心是理解 “层次扩散” 与 “深度探索 + 回溯” 的本质,并根据问题场景选择合适的算法。

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

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

相关文章

【面试场景题】日志去重与统计系统设计

文章目录题目场景描述要求问题考察点解答思考一、核心解决方案&#xff08;基础版&#xff0c;单节点32GB内存、10台节点&#xff09;1. 整体架构选型2. 关键步骤详解&#xff08;1&#xff09;数据分片&#xff1a;解决“数据量大&#xff0c;单节点处理不了”的问题&#xff…

【Day 16】Linux-性能查看

目录 一、Stress系统压力测试工具 二、性能查看 &#xff08;一&#xff09;查看CPU # nproc # lscpu # top # uptime # mpstat 数字1 数字2 &#xff08;二&#xff09;查看内存 # dmidecode -t memory | less # free -h # …

【ICCV2017】Deformable Convolutional Networks

一、摘要尽管卷积神经网络&#xff08;CNN&#xff09;在视觉识别任务上取得巨大成功&#xff0c;但其固有的固定几何结构&#xff08;固定卷积采样网格、固定池化窗口、固定 RoI 划分&#xff09;严重限制了对未知几何变换&#xff08;尺度、姿态、形变、视角变化&#xff09;…

echarts在前后端分离项目中的实践与应用

目录 一、ECharts简介 二、后端数据接口设计 三、数据结构设计 1. 柱状图数据结构 2. 饼图数据结构 四、后端实现要点 五、前端ECharts配置解析 1. 柱状图配置 2. 饼图配置 六、最佳实践建议 七、总结 一、ECharts简介 ECharts是百度开源的一个基于JavaScript的可视…

SQL 四大语言分类详解:DDL、DML、DCL、DQL

SQL&#xff08;结构化查询语言&#xff09;通常被分为四种主要类型&#xff0c;每种类型负责不同的数据库操作。下面我将详细介绍这四类SQL语言的语法和用途。一、DDL (Data Definition Language) 数据定义语言功能&#xff1a;定义和管理数据库对象结构&#xff08;表、视图、…

ESP-idf框架下的HTTP服务器\HTML 485温湿度采集并长传

项目描述:本项目采用485采集温湿度以及电压电流等,485模块分别为下图,串口转485模块采用自动收发模块,ESP32工作在AP热点模式,通过手机连接esp32的热点来和esp进行数据通讯,使用esp32作为HTTP服务器缺陷:项目的最终HTML页面代码可发给AI让其写注释#include "freertos/Free…

雅江工程解锁墨脱秘境:基础条件全展示(区位、地震、景点、天气)

目录 前言 一、区位信息 1、空间位置 2、区位介绍 二、地震信息 1、历史地震信息 2、5.0级以上大地震 三、景点信息 1、景点列表分布 2、4A级以上景点 四、天气信息 1、天气实况 2、天气应对挑战 五、总结 前言 相信最近大家对雅江电站的超级大工程项目应该有所耳…

​​机器学习贝叶斯算法

​​一、引言​​在当今机器学习领域&#xff0c;贝叶斯算法犹如一颗璀璨的明星。你是否想过&#xff0c;垃圾邮件过滤系统是如何准确判断一封邮件是否为垃圾邮件的呢&#xff1f;这背后可能就有贝叶斯算法的功劳。今天&#xff0c;我们就一同走进贝叶斯算法的世界&#xff0c;…

Chisel芯片开发入门系列 -- 18. CPU芯片开发和解释8(流水线架构的代码级理解)

以【5 Stage pipeline CPU】搜索图片&#xff0c;选取5幅有代表性的图列举如下&#xff0c;并结合Chisel代码进行理解和点评。 图1&#xff1a;原文链接如下 https://acsweb.ucsd.edu/~dol031/posts/update/2023/04/10/5stage-cpu-pipeline.html 点评&#xff1a;黑色的部分…

Docker容器中文PDF生成解决方案

在Docker容器中生成包含中文内容的PDF文件时&#xff0c;经常遇到中文字符显示为方块或乱码的问题。本文将详细介绍如何在Docker环境中配置中文字体支持&#xff0c;实现完美的中文PDF生成。 问题现象 当使用wkhtmltopdf、Puppeteer或其他PDF生成工具时&#xff1a; 中文字符…

2.java集合,线程面试题(已实践,目前已找到工作)

1线程的创建方式 继承Thread类实现Runnable接口实现Callable接口 2.这三种方式在项目中的使用有哪些&#xff0c;一般都是怎么用的 继承thread类实现线程的方式通过实现run方法来实现线程&#xff0c;通过run进行线程的启用实现runnable方法实现run方法&#xff0c;然后通过thr…

站在前端的角度,看鸿蒙页面布局

从Web前端转向鸿蒙&#xff08;HarmonyOS&#xff09;开发时&#xff0c;理解其页面布局的相似与差异是快速上手的核心。鸿蒙的ArkUI框架在布局理念上与Web前端有诸多相通之处&#xff0c;但也存在关键区别。以下从五个维度系统分析&#xff1a; &#x1f4e6; 一、盒子模型&a…

JavaWeb遗传算法、TSP、模拟退火、ACO算法等实战应用

Java Web中实现遗传算法的应用 以下是关于Java Web中实现遗传算法的应用场景和实例的整理,涵盖不同领域的解决方案和实现方法: 遗传算法基础结构 在Java Web中实现遗传算法通常需要以下核心组件: 种群初始化:随机生成初始解集。 适应度函数:评估个体优劣。 选择操作:轮…

【图像算法 - 09】基于深度学习的烟雾检测:从算法原理到工程实现,完整实战指南

一、项目背景与需求 视频介绍 【图像算法 - 09】基于深度学习的烟雾检测&#xff1a;从算法原理到工程实现&#xff0c;完整实战指南今天我们使用深度学习来训练一个烟雾明火检测系统。这次我们使用了大概一万五千张图片的数据集训练了这次的基于深度学习的烟雾明火检测模型&a…

间接制冷技术概念及特征

1、基本概念 (1)间接制冷技术即二次制冷技术。常规做法:二次冷却液储液罐增加放置于制冷系统管路,促使冷量再快捷的传递给载冷剂,继而载冷剂冷量促使冷库达到制冷效果。间接制冷技术:通过常压的二次冷却介质进行大循环传送冷量,在直接制冷剂不易应用的位置或者不可运用直…

Antlr学习笔记 01、maven配置Antlr4插件案例Demo

文章目录前言源码插件描述pom引入插件案例&#xff1a;实现hello 标识符 案例1、引入Antlr4的pom运行依赖2、定义语义语法&#xff0c;配置.g4文件实现java代码3、编写完之后&#xff0c;执行命令实现编译4、编写单测测试使用参考文章资料获取前言 博主介绍&#xff1a;✌目前…

PostGIS面试题及详细答案120道之 (101-110 )

《前后端面试题》专栏集合了前后端各个知识模块的面试题&#xff0c;包括html&#xff0c;javascript&#xff0c;css&#xff0c;vue&#xff0c;react&#xff0c;java&#xff0c;Openlayers&#xff0c;leaflet&#xff0c;cesium&#xff0c;mapboxGL&#xff0c;threejs&…

第十七天:原码、反码、补码与位运算

原码、反码、补码与位运算 一、原码、反码、补码 1、原码 定义&#xff1a;原码是一种简单的机器数表示法。对于一个有符号整数&#xff0c;最高位为符号位&#xff0c; 0 表示正数&#xff0c; 1 表示负数&#xff0c;其余位表示数值的绝对值。示例&#xff1a;以 8 位二进制…

一次完整的 Docker 启动失败排错之旅:从 `start-limit` 到 `network not found

一次完整的 Docker 启动失败排错之旅&#xff1a;从 start-limit 到 network not found 你是否也曾自信地敲下 sudo systemctl start docker&#xff0c;却只得到一个冰冷的 failed&#xff1f;这是一个开发者和运维工程师都可能遇到的场景。本文将通过一个真实的排错案例&…

Tdengine 时序库年月日小时分组汇总问题

年月分组select to_char(collection_time ,"yyyy-mm") AS date, cast(SUM(a.stage_value)as DOUBLE) as stage_value from TABLE GROUP BY date年月日分组select to_char(collection_time ,"yyyy-mm-dd") AS date, SUM(a.stage_value)as DOUBLE) as stage_…