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

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

一、核心编程思想

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

二、通用编程框架

  1. BFS 框架(模板)
    基础模板(树 / 图通用)
    def bfs(start, target):
        # 初始化队列(存储待处理节点)
        from collections import deque
        queue = 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 deque
        queue = 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 deque
        dead = set(deadends)
        start = "0000"
        if start in dead:
            return -1
        if start == target:
            return 0
        
        # BFS初始化
        queue = deque([start])
        visited = set([start])
        steps = 0
        
        while queue:
            steps += 1
            level_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) % 10
                        next_str = current[:i] + str(new_digit) + current[i+1:]
                        if next_str == target:
                            return steps
                        if 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 0
        rows, cols = len(grid), len(grid[0])
        visited = [[False for _ in range(cols)] for _ in range(rows)]
        count = 0
        
        def dfs(i, j):
            # 越界或非陆地或已访问,直接返回
            if i < 0 or i >= rows or j < 0 or j >= cols or grid[i][j] == '0' or visited[i][j]:
                return
            visited[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 += 1
                    dfs(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())
                return
            for i in range(n):
                if not visited[i]:
                    # 选择当前数字
                    visited[i] = True
                    path.append(nums[i])
                    # 递归下一层
                    backtrack(path, visited)
                    # 回溯:撤销选择
                    path.pop()
                    visited[i] = False
        
        backtrack([], [False]*n)
        return result
    

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

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

贝塔西塔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值