🌊 BFS — 逐层搜索的稳健力量

BFS是最短路径的保证,层序遍历、岛屿计数、单词接龙的核心算法

📖 BFS的核心概念

广度优先搜索(BFS, Breadth-First Search)从起点出发,逐层向外扩展,先访问距离近的节点,再访问距离远的。BFS 的核心特性是最短路径保证——在无权图中,BFS 第一次到达目标节点的路径一定是最短的。BFS 用队列实现,时间复杂度 O(V+E)。

BFS vs DFS: BFS (广度优先): 逐层扩展 ┌─────────────────────────────┐ │ 层0: A │ │ 层1: B C │ │ 层2: D E F G │ │ 层3: H I ... │ └─────────────────────────────┘ 访问顺序: A → B,C → D,E,F,G → ... 特点: 先近后远, 保证最短路径 DFS (深度优先): 一条路走到底 ┌─────────────────────────────┐ │ A → B → D → H │ │ → I │ │ → E │ │ → C → F │ │ → G │ └─────────────────────────────┘ 访问顺序: A→B→D→H→I→E→C→F→G 特点: 深入优先, 不保证最短 何时用BFS: ✓ 最短路径(无权图) ✓ 层序遍历 ✓ 连通分量 ✓ 状态空间搜索(最少步数)

1. BFS 通用模板

from collections import deque

def bfs(start):
    queue = deque([start])
    visited = {start}
    
    while queue:
        node = queue.popleft()
        # 处理当前节点
        
        for neighbor in get_neighbors(node):
            if neighbor not in visited:
                visited.add(neighbor)
                queue.append(neighbor)

2. BFS 的常见变体

变体特点示例
标准BFS找最短路径LC 1091, LC 127
层序BFS按层记录,区分层级LC 102, LC 515
多源BFS多个起点同时扩展LC 1162, LC 542
双向BFS从两端同时搜索LC 127 优化

🎯 题目一:二叉树的层序遍历 (LC 102)

题目描述:给你二叉树的根节点 root,返回其节点值的层序遍历。

示例: 3 / \ 9 20 / \ 15 7 BFS过程: 队列: [3] → 弹出3, 入队9,20 → 层0: [3] 队列: [9, 20] → 弹出9(无子),20入15,7 → 层1: [9,20] 队列: [15, 7] → 弹出15,7(无子) → 层2: [15,7] 结果: [[3], [9, 20], [15, 7]]

思路分析

层序 BFS:
1. 队列初始化放入根节点
2. 每轮记录当前层大小,弹出该数量节点
3. 收集当前层值,子节点入队
时间复杂度:O(n),空间复杂度:O(w)

代码实现

class Solution:
    def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
        from collections import deque
        if not root:
            return []
        result = []
        queue = deque([root])
        while queue:
            level = []
            for _ in range(len(queue)):
                node = queue.popleft()
                level.append(node.val)
                if node.left:
                    queue.append(node.left)
                if node.right:
                    queue.append(node.right)
            result.append(level)
        return result

复杂度分析

时间复杂度:O(n)

空间复杂度:O(w)

变体练习

🎯 题目二:单词接龙 (LC 127)

题目描述:字典 wordList 中从 beginWordendWord 的最短转换序列的长度。每次转换只能改变一个字母,转换后的单词必须在字典中。

示例: beginWord = "hit", endWord = "cog" wordList = ["hot","dot","dog","lot","log","cog"] BFS搜索: 层0: hit 层1: hot (hit→hot, 改i→o) 层2: dot, lot (hot→dot, hot→lot) 层3: dog, log (dot→dog, lot→log) 层4: cog (dog→cog 或 log→cog) 最短转换序列长度 = 5 (5个单词,4次转换) 返回 5 优化: 预处理邻接表 *it → [hit] h*t → [hot] hi* → [hit] *ot → [hot,dot,lot] d*t → [dot] do* → [dot,dog] ...以此类推

思路分析

BFS 最短路径:
1. 预处理:将每个单词的每个位置替换为 *,构建通配符 → 单词列表的映射
2. BFS:从 beginWord 开始,每步找所有只差一个字母的单词
3. 第一次到达 endWord 时的层数即为最短路径
时间复杂度:O(M² × N),M 为单词长度,N 为字典大小

代码实现

class Solution:
    def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int:
        from collections import deque, defaultdict
        if endWord not in wordList:
            return 0
        
        # 预处理: 通配符 → 单词列表
        adj = defaultdict(list)
        for word in wordList + [beginWord]:
            for i in range(len(word)):
                pattern = word[:i] + '*' + word[i+1:]
                adj[pattern].append(word)
        
        # BFS
        queue = deque([(beginWord, 1)])
        visited = {beginWord}
        
        while queue:
            word, steps = queue.popleft()
            if word == endWord:
                return steps
            for i in range(len(word)):
                pattern = word[:i] + '*' + word[i+1:]
                for neighbor in adj[pattern]:
                    if neighbor not in visited:
                        visited.add(neighbor)
                        queue.append((neighbor, steps + 1))
        return 0

复杂度分析

时间复杂度:O(M² × N)

空间复杂度:O(M² × N)

变体练习

🎯 题目三:岛屿数量 (LC 200)

题目描述:给你一个由 '1'(陆地)和 '0'(水)组成的二维网格,请你计算网格中岛屿的数量。岛屿是被水包围的陆地,由相邻的陆地连接而成。

示例: 1 1 0 0 0 1 1 0 0 0 0 0 1 0 0 0 0 0 1 1 岛屿1: 左上4个1 岛屿2: 中间1个1 岛屿3: 右下2个1 共3个岛屿 BFS过程: 扫描网格, 遇到'1'→ count++, BFS标记整个岛屿为已访问 遍历4个方向的邻居, '1'的入队并标记

思路分析

BFS/DFS 淹没岛屿:
1. 遍历网格,遇到 '1' → 岛屿数+1
2. BFS/DFS 从该点出发,将所有相连的 '1' 标记为已访问(或改为 '0')
3. 继续扫描,直到遍历完整个网格
时间复杂度:O(m×n),空间复杂度:O(m×n)(最坏)

代码实现

class Solution:
    def numIslands(self, grid: List[List[str]]) -> int:
        if not grid:
            return 0
        from collections import deque
        rows, cols = len(grid), len(grid[0])
        count = 0
        
        for r in range(rows):
            for c in range(cols):
                if grid[r][c] == '1':
                    count += 1
                    # BFS 淹没该岛屿
                    queue = deque([(r, c)])
                    grid[r][c] = '0'
                    while queue:
                        row, col = queue.popleft()
                        for dr, dc in [(1,0),(-1,0),(0,1),(0,-1)]:
                            nr, nc = row + dr, col + dc
                            if 0 <= nr < rows and 0 <= nc < cols and grid[nr][nc] == '1':
                                grid[nr][nc] = '0'
                                queue.append((nr, nc))
        return count

复杂度分析

时间复杂度:O(m×n) — 每个格子最多访问一次

空间复杂度:O(min(m,n)) — BFS 队列最大长度

变体练习

成就解锁:BFS探险家 — 掌握层序遍历、最短路径、连通分量三大BFS应用
LeetCode AC验证:LC 102 Binary Tree Level Order Traversal ✅ | LC 127 Word Ladder ✅ | LC 200 Number of Islands ✅

📝 课后练习

  1. LC 695 岛屿的最大面积(BFS/DFS)
  2. LC 752 打开转盘锁(BFS状态搜索)
  3. LC 1091 二进制矩阵中的最短路径
  4. LC 1162 地图分析(多源BFS)
  5. LC 542 01 矩阵(多源BFS)

🔑 本课要点回顾

📚 扩展阅读

思考题:为什么 visited 集合必须在入队时就标记,而不能在弹出时标记?如果在弹出时标记会发生什么?提示:同一个节点会被多次入队,导致重复处理。

🔬 BFS的进阶技巧

BFS 还有一些进阶技巧,掌握后可以解决更复杂的搜索问题。

1. 双向BFS

从起点和终点同时搜索,当两端的搜索树相遇时找到最短路径。搜索空间从 O(b^d) 降到 O(2×b^(d/2)),指数级优化!

def bidirectional_bfs(begin, end, graph):
    front = {begin}
    back = {end}
    visited = {begin, end}
    steps = 0
    
    while front and back:
        # 始终从较小的集合扩展
        if len(front) > len(back):
            front, back = back, front
        
        next_front = set()
        for node in front:
            for neighbor in graph[node]:
                if neighbor in back:
                    return steps + 1
                if neighbor not in visited:
                    visited.add(neighbor)
                    next_front.add(neighbor)
        front = next_front
        steps += 1
    return -1

2. 多源BFS

多个起点同时入队,适用于"到最近源的距离"类问题。所有源同时开始扩展,第一次到达某个格子时的距离就是到最近源的距离。

多源BFS 本质上是"超级源点"思想:想象一个虚拟源点与所有真实源点相连(边权为0),从虚拟源点做 BFS 等价于多源 BFS。
思考题:LC 1162 地图分析要求找到离陆地最远的海洋格子,如何用多源BFS解决?提示:所有陆地点同时入队,最后被访问到的海洋格子就是答案。