algorithm: DFS 示例及pseduocode及visited 5/999
🔹 图 (Graph)
0 -- 1 -- 2
| |
3 -- 4
- 节点:0,1,2,3,4
- 从
0
开始做 DFS(递归方式)
🔹 DFS 步骤
DFS(0) → DFS(1) → DFS(2) → 回溯 → DFS(4) → DFS(3)
🔹 表格演示 DFS 执行过程
步骤 | 当前节点 | 动作 | 已访问 (visited) | 栈/递归路径 |
---|---|---|---|---|
1 | 0 | 访问 0 | {0} | [0] |
2 | 0 → 1 | 访问 1 | {0,1} | [0,1] |
3 | 1 → 2 | 访问 2 | {0,1,2} | [0,1,2] |
4 | 2 | 无新邻居 → 回溯 | {0,1,2} | [0,1] |
5 | 1 → 4 | 访问 4 | {0,1,2,4} | [0,1,4] |
6 | 4 → 3 | 访问 3 | {0,1,2,4,3} | [0,1,4,3] |
7 | 3 | 无新邻居 → 回溯 | {0,1,2,4,3} | [0,1,4] |
8 | 4 | 无新邻居 → 回溯 | {0,1,2,4,3} | [0,1] |
9 | 1 | 所有邻居访问完 → 回溯 | {0,1,2,4,3} | [0] |
10 | 0 | 所有邻居访问完 → 结束 | {0,1,2,4,3} | [] |
🔹 最终 DFS 遍历顺序
0 → 1 → 2 → 4 → 3
这样你就可以清楚看到:DFS 是“走到最深,再回溯”。
表格中的 栈/递归路径 就是 DFS 的精髓。
Perfect 👍 Let’s do DFS (Depth-First Search) pseudocode.
I’ll give you both the recursive and iterative (stack-based) versions, since both are common.
🔹 Recursive DFS Pseudocode
DFS(Graph, node, visited):if node is already in visited:returnmark node as visitedprocess(node) // e.g., print it, check if targetfor each neighbor in Graph[node]:DFS(Graph, neighbor, visited)
🔹 Iterative DFS (using stack)
DFS(Graph, start):create a stack Screate a set visitedpush start onto Swhile S is not empty:node ← pop from Sif node not in visited:mark node as visitedprocess(node) // e.g., print it, check targetfor each neighbor in Graph[node]:if neighbor not in visited:push neighbor onto S
🔹 Key Differences from BFS
BFS | DFS |
---|---|
Uses a queue (FIFO) | Uses a stack (LIFO) or recursion |
Explores level by level | Explores deep into one branch before backtracking |
Guarantees shortest path in unweighted graph | Does not guarantee shortest path, but good for exploring all possibilities |
🔹 Example Walkthrough (Graph)
Graph:
0 -- 1 -- 2
| |
3 -- 4
- Start =
0
- DFS order (recursive):
0 → 1 → 2 → 4 → 3
- BFS order (for comparison):
0 → 1 → 3 → 2 → 4
✅ Summary
- DFS can be written naturally with recursion.
- Or simulate recursion with a stack.
- Core idea = “go as deep as possible, then backtrack.”
🔹 为什么 DFS 需要 visited
标记?
-
避免死循环
-
图(Graph)可能有环,比如:
A -- B\ /C
-
如果没有
visited
,DFS(A) 会去 B → 再回 A → 再去 B → … 无限循环。 -
visited
用来阻止这种情况:节点一旦访问过,就不再重复进入。
-
-
保证每个节点只处理一次
- DFS 的目标通常是“遍历所有节点”。
- 如果没有
visited
,你可能会多次访问同一个节点,效率变成 指数级,甚至永远不结束。
-
区分路径探索和回溯
- 在回溯类题目(如数独、八皇后)中,
visited
不光避免死循环,还用来“标记当前路径上使用的节点”。 - 回溯时需要“撤销”这个标记,保证别的分支还能用这个节点。
- 在回溯类题目(如数独、八皇后)中,
🔹 举例说明
假设图:
0 -- 1 -- 2
| |
3 --------4
如果没有 visited
:
- 从
0
出发 → 到1
→ 再到2
→ 再到4
→ 回到3
→ 再到0
→ 又开始循环。 - DFS 进程就死锁在环里了。
如果有 visited
:
- 第一次访问
0
时,标记0
- 下次遇到
0
时直接跳过,不再深入。 - 遍历顺利完成。
🔹 DFS 和 BFS 都需要 visited
吗?
-
图:需要(因为可能有环)。
-
树:不需要(树无环,父节点不会被回溯成子节点)。
- 所以很多二叉树题目里,你没见过
visited
。
- 所以很多二叉树题目里,你没见过
✅ 结论
DFS 需要 visited
,是为了:
- 防止在有环的图里无限循环;
- 保证每个节点只访问一次,避免重复工作;
- 在回溯问题中,用来限制路径和恢复状态。
明白了,你问的是这一行:
DFS(neighbor, visited)
🔹 为什么要这一步?
-
核心目的:递归探索邻居节点
- DFS(深度优先搜索)的思想是“走到最深再回头”。
- 当我们访问一个节点
node
后,必须去访问它所有未访问的邻居。 DFS(neighbor, visited)
就是执行这一步:进入邻居节点的深度搜索。
-
保证完整遍历
- 如果不调用 DFS 遍历邻居,就只能访问当前节点,无法访问整张图或整棵树。
- 递归调用可以不断深入,直到访问完最深的分支,再回溯到上层。
-
实现回溯
- 每一次递归调用相当于走入一个新的分支。
- 当这一分支访问完毕,函数自动返回上层调用 → 回到原来的节点继续访问其他邻居。
- 这就是“回溯”的关键机制。
-
结合
visited
防止重复- 在递归前检查
neighbor not in visited
,防止环路导致无限递归。 - 所以每一次递归都是安全的,只会访问尚未访问的节点。
- 在递归前检查
🔹 用一句话总结
这一行的作用就是 深入探索当前节点的每一个邻居,确保整个图/树被完整遍历,同时利用递归实现回溯。
用 表格 来展示 DFS 中递归调用 DFS(neighbor, visited)
的作用,以及它如何实现深入探索与回溯。
假设图如下:
0 -- 1 -- 2
| |
3 -- 4
从节点 0
开始 DFS。
表格说明 DFS 递归执行过程
步骤 | 当前函数调用 (节点) | 动作 | visited | 输出顺序 | 递归栈 |
---|---|---|---|---|---|
1 | DFS(0) | 访问 0 | {0} | [0] | [0] |
2 | DFS(0) → DFS(1) | 访问 1 | {0,1} | [0,1] | [0,1] |
3 | DFS(1) → DFS(2) | 访问 2 | {0,1,2} | [0,1,2] | [0,1,2] |
4 | DFS(2) → 无邻居可访问 | 回溯 | {0,1,2} | [0,1,2] | [0,1] |
5 | DFS(1) → DFS(4) | 访问 4 | {0,1,2,4} | [0,1,2,4] | [0,1,4] |
6 | DFS(4) → DFS(3) | 访问 3 | {0,1,2,3,4} | [0,1,2,4,3] | [0,1,4,3] |
7 | DFS(3) → 无邻居可访问 | 回溯 | {0,1,2,3,4} | [0,1,2,4,3] | [0,1,4] |
8 | DFS(4) → 所有邻居访问完 | 回溯 | {0,1,2,3,4} | [0,1,2,4,3] | [0,1] |
9 | DFS(1) → 所有邻居访问完 | 回溯 | {0,1,2,3,4} | [0,1,2,4,3] | [0] |
10 | DFS(0) → DFS(3) | 已访问 → 跳过 | {0,1,2,3,4} | [0,1,2,4,3] | [0] |
11 | DFS(0) → 所有邻居访问完 | 结束 | {0,1,2,3,4} | [0,1,2,4,3] | [] |
🔹 说明
-
当前函数调用 (节点)
- 显示每次递归进入哪个节点。
-
动作
- “访问”表示处理节点(打印/保存);
- “回溯”表示递归返回上一层,继续访问其他邻居。
-
visited
- 标记哪些节点已经访问过,避免重复访问或死循环。
-
输出顺序
- 记录遍历节点顺序。
-
递归栈
- 显示当前 DFS 调用栈(最右边是栈顶)。
- 当一个分支访问完成,栈会弹出 → 实现回溯。
✅ 结论
- 每一次
DFS(neighbor, visited)
都是“走向下一层分支”。 - 当分支访问完毕,函数返回 → 栈弹出 → 自动回到上层节点 → 访问下一个邻居。
- 这就是 DFS 的 递归 + 回溯机制 在表格中的直观体现。