Skip to content

搜索

本章目标:掌握 BFS、DFS 及其变体(双向 BFS、A*、IDDFS),能解决最短路径和状态空间搜索问题。

你将学到

  • BFS 求最短步数
  • DFS 找所有解
  • 双向 BFS 加速
  • A* 启发式搜索
  • 迭代加深 IDDFS

1. 概念

搜索算法是在状态空间中寻找目标解的方法。根据探索顺序不同分为:

  • BFS(广度优先搜索):一层一层扩展,天然适合找最短路径(无权图)
  • DFS(深度优先搜索):一条路走到底,适合找所有解或连通性问题

高级搜索策略:

  • 双向 BFS:从起点和终点同时搜索,在中间相遇,大幅减少搜索空间
  • A 搜索*:带启发函数的优先搜索,用贪心策略引导方向
  • 迭代加深 IDDFS:结合 DFS 空间小和 BFS 找最优的特性

搜索的关键是定义状态状态转移。在网格问题中,状态就是坐标;在复杂问题中,状态可能包含多个变量。

2. 核心模板

BFS 最短路径

python
from collections import deque

def bfs_shortest_path(start, target, get_neighbors):
    """通用 BFS 找最短步数"""
    queue = deque([(start, 0)])
    visited = {start}
    while queue:
        state, steps = queue.popleft()
        if state == target:
            return steps
        for neighbor in get_neighbors(state):
            if neighbor not in visited:
                visited.add(neighbor)
                queue.append((neighbor, steps + 1))
    return -1  # 不可达

DFS 模板

python
def dfs_all_paths(state, target, path, visited, get_neighbors, result):
    """DFS 找所有路径"""
    if state == target:
        result.append(path[:])
        return
    for neighbor in get_neighbors(state):
        if neighbor not in visited:
            visited.add(neighbor)
            path.append(neighbor)
            dfs_all_paths(neighbor, target, path, visited, get_neighbors, result)
            path.pop()
            visited.remove(neighbor)

双向 BFS

python
def bidirectional_bfs(start, target, get_neighbors):
    """双向 BFS,从两端同时搜索"""
    if start == target:
        return 0
    front = {start}
    back = {target}
    visited = {start, target}
    step = 0
    while front and back:
        step += 1
        # 始终扩展较小的一端
        if len(front) > len(back):
            front, back = back, front
        next_front = set()
        for state in front:
            for neighbor in get_neighbors(state):
                if neighbor in back:
                    return step + 1  # 两端相遇(或 step,视定义)
                if neighbor not in visited:
                    visited.add(neighbor)
                    next_front.add(neighbor)
        front = next_front
    return -1

复杂度

  • BFS/DFS:时间 O(V + E),空间 O(V)
  • 双向 BFS:时间 O(b^(d/2)),b 为分支因子,d 为解深度

3. 经典题目精讲

LeetCode 127 · 单词接龙

题意:给定起始单词、目标单词和字典,每次变一个字母,求最短变换序列长度。

思路:BFS,每个单词是一个状态,变换一个字母得到邻居。

python
from collections import deque

def ladderLength(beginWord, endWord, wordList):
    wordSet = set(wordList)
    if endWord not in wordSet:
        return 0
    queue = deque([(beginWord, 1)])
    visited = {beginWord}
    while queue:
        word, length = queue.popleft()
        if word == endWord:
            return length
        # 枚举所有可能的变换单词
        for i in range(len(word)):
            for c in 'abcdefghijklmnopqrstuvwxyz':
                new_word = word[:i] + c + word[i+1:]
                if new_word in wordSet and new_word not in visited:
                    visited.add(new_word)
                    queue.append((new_word, length + 1))
    return 0

LeetCode 695 · 岛屿的最大面积

题意:给定二维网格,找最大岛屿面积。

思路:DFS/BFS 遍历每个连通区域,计数面积。

python
def maxAreaOfIsland(grid):
    rows, cols = len(grid), len(grid[0])
    max_area = 0

    def dfs(r, c):
        if r < 0 or r >= rows or c < 0 or c >= cols or grid[r][c] == 0:
            return 0
        grid[r][c] = 0  # 标记已访问
        return 1 + dfs(r+1, c) + dfs(r-1, c) + dfs(r, c+1) + dfs(r, c-1)

    for r in range(rows):
        for c in range(cols):
            if grid[r][c] == 1:
                max_area = max(max_area, dfs(r, c))
    return max_area

LeetCode 773 · 滑动谜题

题意:2×3 拼图,每步移动一格,求还原到 [[1,2,3],[4,5,0]] 的最少步数。

思路:BFS,把棋盘序列化为元组作为状态。

python
from collections import deque

def slidingPuzzle(board):
    target = (1, 2, 3, 4, 5, 0)
    # 展平棋盘
    start = tuple(board[0] + board[1])
    # 每个位置可以移动到的相邻位置
    neighbors = {0: [1, 3], 1: [0, 2, 4], 2: [1, 5],
                 3: [0, 4], 4: [1, 3, 5], 5: [2, 4]}

    queue = deque([(start, start.index(0), 0)])  # (状态, 0的位置, 步数)
    visited = {start}
    while queue:
        state, zero_pos, steps = queue.popleft()
        if state == target:
            return steps
        for neighbor_pos in neighbors[zero_pos]:
            new_state = list(state)
            new_state[zero_pos], new_state[neighbor_pos] = new_state[neighbor_pos], new_state[zero_pos]
            new_state = tuple(new_state)
            if new_state not in visited:
                visited.add(new_state)
                queue.append((new_state, neighbor_pos, steps + 1))
    return -1

4. 易错点

  • ⚠️ BFS 入队时立即标记 visited,否则可能重复入队
  • ⚠️ DFS 递归深度过大可能栈溢出,需改为迭代
  • ⚠️ 双向 BFS 相遇时返回的步数需要仔细计算(根据状态定义)
  • ⚠️ 状态序列化要保证唯一性,元组比列表适合做字典 key

5. 推荐刷题清单

题号题名难度关键
127单词接龙HardBFS
200岛屿数量MediumDFS/BFS
695岛屿的最大面积MediumDFS
787K 站中转内最便宜航班MediumBellman-Ford/BFS
773滑动谜题HardBFS 状态搜索

延伸阅读

基于 VitePress 构建 · 部署于 Cloudflare Pages