DFS、BFS、回溯与 visited 数组在搜索问题中的应用
这两天一直在刷算法题,如何在最短时间速成,那就是对同类型题总结。
要理解 DFS、BFS、回溯、visited 数组 的关系,需要先明确每个概念的核心定位,再看它们如何在 “搜索问题” 中协同或区分 —— 本质上,它们都是解决 “从初始状态找到目标状态” 的工具,只是适用场景和实现逻辑不同,而 visited 数组是保障搜索正确性的通用辅助手段。
一、先明确每个概念的核心定义
在分析关系前,先拆解每个概念的本质,避免混淆:
| 概念 | 核心定位 | 典型场景 |
| DFS | 深度优先搜索(Depth-First Search),是一种 “一条路走到黑” 的搜索策略。 | 迷宫路径、连通性判断、拓扑排序 |
| BFS | 广度优先搜索(Breadth-First Search),是一种 “逐层扩散” 的搜索策略。 | 最短路径(无权图)、层序遍历 |
| 回溯 | 是一种 “尝试 - 失败 - 回退” 的解题思想,常基于 DFS 实现,核心是 “撤销选择”。 | 组合、排列、子集、迷宫回溯 |
| visited 数组 | 是标记 “已访问状态” 的辅助工具,防止重复访问或无限循环,保障搜索正确性。 | 所有图 / 矩阵搜索问题 |
二、四者的核心关系:分层理解
可以按 “策略(DFS/BFS)→ 思想(回溯)→ 工具(visited) ” 的层级理解它们的关系,具体可分为 3 个维度:
1. DFS 与 BFS:并列的搜索策略,互斥但互补
DFS 和 BFS 是解决 “遍历 / 搜索” 问题的两种 基础且互斥的策略—— 同一问题中通常二选一,选择的核心依据是问题需求:
| 对比维度 | DFS(深度优先) | BFS(广度优先) |
| 搜索逻辑 | 从起点出发,优先深入下一层(“一条路走到黑”),走不通再回退。 | 从起点出发,先遍历当前层所有节点,再遍历下一层(“逐层扩散”)。 |
| 实现方式 | 递归(天然栈结构)或手动栈 | 队列(FIFO,保证层级顺序) |
| 核心优势 | 空间复杂度低(栈深度 ≤ 最大路径长度);适合 “找任意路径”“回溯类问题”。 | 能找到无权图的最短路径(首次到达目标节点时,路径长度最小)。 |
| 典型场景 | 迷宫找任意路径、组合 / 排列问题、连通分量判断。 | 迷宫找最短路径、二叉树层序遍历、社交网络 “好友推荐”(找最近联系人)。 |
例:同一迷宫问题
- 若只需 “找到任意一条通路”:选 DFS(实现简单,递归即可);
- 若需 “找到最短通路”:必须选 BFS(DFS 可能深入长路径,无法保证最短)。
2. 回溯与 DFS:“思想” 与 “实现载体” 的关系
回溯不是一种独立的搜索策略,而是一种 “尝试 - 回退” 的解题思想,其核心场景是 “需要枚举所有可能,且过程中需撤销无效选择”(如组合、排列、子集问题)。
而 DFS 的 “递归回退” 特性,恰好为回溯思想提供了 天然的实现载体—— 递归的 “调用栈” 会记录每一步的选择,递归返回时自然回到上一步,此时只需 “撤销上一步的选择”,就是完整的回溯流程。
可以理解为:
回溯 = DFS 搜索 + 撤销选择
举个例子(组合问题:从 [1,2,3] 选 2 个元素):
- 尝试选 1 → 递归选下一个元素(可选 2 或 3);
- 选 1+2:记录组合 [1,2],递归结束后 撤销选择(移除 2);
- 再选 1+3:记录组合 [1,3],递归结束后 撤销选择(移除 3);
- 最后 撤销选择(移除 1),尝试选 2 → 后续流程同理。
这里的 “撤销选择” 就是回溯的核心,而整个流程是通过 DFS 递归实现的。
注意:回溯几乎只基于 DFS 实现,不会基于 BFS—— 因为 BFS 是 “逐层扩散”,路径分散,无法高效 “回退” 到上一步撤销选择。
3. visited 数组:所有搜索策略的 “通用安全锁”
visited 数组(或标记逻辑)的核心作用是 防止重复访问同一个状态 / 节点,避免无限循环或重复计算,它是 DFS、BFS、回溯的 “通用辅助工具”,但具体用法会根据场景微调:
(1)在 “无状态重复” 的场景:visited 用于标记 “已走节点”
典型场景:矩阵 / 迷宫搜索(迷宫题)。
- 迷宫中每个格子是 “唯一节点”,一旦走过就不能再走(否则会绕圈,比如从 (0,0)→(0,1) 再回 (0,0),无限循环);
- 实现方式:访问格子 (x,y) 时,将 map[x][y] 设为 1(等效于 visited 标记),回溯时再改回 0(撤销标记)。
(2)在 “有状态重复” 的场景:visited 用于标记 “已用元素”
典型场景:排列问题(如从 [1,2,3] 生成全排列)。
- 数组中每个元素是 “唯一元素”,一旦在当前排列中使用过,就不能再用(否则会生成 [1,1,2] 这种无效排列);
- 实现方式:用 boolean[] visited 数组,visited[i] = true 表示第 i 个元素已用,递归回溯时设为 false(撤销标记)。
(3)不同策略下的 visited 共性:
- DFS/BFS/ 回溯都需要 visited:只要存在 “重复访问风险”,就必须用;
- visited 的核心是 “标记状态”:状态可以是 “节点位置”“元素索引”“路径状态” 等,本质是避免无效循环。
三、总结:四者的关系图谱
用一句话串联所有关系:
回溯思想通过 DFS 策略实现,DFS 和 BFS 是并列的搜索策略,三者在解决 “有重复访问风险” 的问题时,都需要借助 visited 数组来保障搜索的正确性。
具体关系图谱如下:
| 搜索问题 ├─ 搜索策略(二选一) │ ├─ DFS(深度优先):适合回溯、找任意路径 │ │ └─ 回溯思想:基于 DFS 实现,核心是“撤销选择” │ └─ BFS(广度优先):适合找最短路径(无权图) └─ 辅助工具 └─ visited 数组:防止重复访问,DFS/BFS/回溯均需使用 |
四、实战:用迷宫问题看四者的应用
- 策略选择:题目说 “迷宫只有一条通道”,只需找任意路径,所以用 DFS;
- 回溯应用:当 DFS 走到死路(如下、右、上、左都走不通),需要 “移除当前位置(path.remove)” 并 “恢复格子状态(map [x][y] = 0)”,这就是回溯;
- visited 作用:用 map[x][y] = 1 标记已走格子(等效于 visited),避免绕圈;
- BFS 不适用:题目不需要最短路径,用 BFS 会增加队列的空间开销,不如 DFS 简洁。
通过这个例子能清晰看到:DFS 是骨架,回溯是血肉,visited 是安全锁,三者协同解决问题,而 BFS 是另一种骨架(适用于其他场景)。
题目:
140

被折叠的 条评论
为什么被折叠?



