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

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

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.dtcms.com/a/316296.html

相关文章:

  • 【Git学习】入门与基础
  • 达芬奇31-40
  • Java技术栈/面试题合集(8)-Redis篇
  • Python爬虫08_Requests聚焦批量爬取图片
  • 如何用分布式架构视角理解宇宙稳定性?从精细调参到微服务的类比思考
  • 微服务架构及常见微服务技术栈
  • PromQL(Prometheus 查询语言)语法详解
  • 2um 精度 + 130mm 深孔扫描:新启航激光频率梳方案重塑 3D 轮廓测量标准
  • 【拓扑序】P6286 [COCI 2016/2017 #1] Cezar|省选-
  • 前端遇到页面卡顿问题,如何排查和解决?
  • DM8日常运维命令总结(四)
  • 游戏画面总是卡顿怎么办 告别延迟畅玩游戏
  • Go语言 泛型
  • TypeScript 中的协变与逆变
  • 203.移除链表元素 707.设计链表 206.反转链表
  • Python ttkbootstrap桌面应用开发示例
  • 创建一个可以edit的graph-node-test的永久登录dashboard的token
  • vue3通过按钮实现横向滚动或鼠标滚动横坐标滚动
  • 预测性维护:数字孪生如何重塑航空航天维修与寿命管理
  • Java技术栈/面试题合集(13)-网络篇
  • 李亚鹤:河南息县:淮河金沙滩开园 再添亲水休闲文旅地
  • 在Maxscript中随机化对象变换、颜色和材质
  • 拖车式变电站:灵活电力供应的“移动堡垒”,驱动全球能源转型新引擎
  • nuxt学习笔记
  • 学术论文命名:策略、方案、方法三者的区别
  • 使用Docker Desktop部署MySQL8.4.3
  • LeetCode 149:直线上最多的点数
  • 深入理解 C 语言中的拷贝函数
  • 多模态新方向|从数据融合到场景落地,解锁视觉感知新范式
  • 智能驾驶再提速!批量苏州金龙L4级自动驾驶巴士交付杭州临平区