DFS 即深度优先搜索(Depth-First Search),是一种用于遍历或搜索树或图的算法,有三种核心的应用场景(基础遍历、回溯、剪枝)。
深度优先搜索(DFS) 是一种遍历或搜索树/图的算法,优先沿着一条路径尽可能深入,直到无法继续再回溯。
实现方式:
递归:隐式利用系统调用栈。
栈模拟:显式使用栈数据结构。
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def dfs_recursive(node):
if not node:
return
print(node.val) # 前序遍历(操作位置可调整)
dfs_recursive(node.left)
dfs_recursive(node.right)
# 示例树结构
root = TreeNode(1, TreeNode(2, TreeNode(4), TreeNode(5)), TreeNode(3))
dfs_recursive(root) # 输出: 1 → 2 → 4 → 5 → 3
def dfs_stack(graph, start):
visited = set()
stack = [start]
while stack:
node = stack.pop()
if node not in visited:
visited.add(node)
print(node) # 处理当前节点
# 将未访问的邻居逆序压入栈(保证顺序)
for neighbor in reversed(graph[node]):
if neighbor not in visited:
stack.append(neighbor)
# 示例图结构
graph = {
'A': ['B', 'C'],
'B': ['D', 'E'],
'C': ['F'],
'D': [],
'E': ['F'],
'F': []
}
dfs_stack(graph, 'A') # 输出: A → C → F → B → E → D
遍历顺序:前序、中序、后序遍历(树结构)。
避免重复访问:使用 visited
集合记录已访问节点(图结构)。
应用场景:路径存在性判断、拓扑排序、连通分量分析。
回溯法 是 DFS 的一种扩展,用于求解 组合、排列、子集 等需要穷举所有可能解的问题。
核心思想:通过递归尝试所有可能的选择,并在发现当前路径无法得到解时撤销(回溯)最后一步选择。
def permute(nums):
def backtrack(path, used):
if len(path) == len(nums):
res.append(path.copy()) # 记录有效解
return
for i in range(len(nums)):
if not used[i]:
used[i] = True # 做出选择
path.append(nums[i])
backtrack(path, used) # 递归
path.pop() # 撤销选择(回溯)
used[i] = False
res = []
backtrack([], [False]*len(nums))
return res
print(permute([1, 2, 3]))
# 输出: [[1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1]]
状态管理:通过 path
记录当前路径,used
记录已选元素。
撤销操作:递归返回后必须撤销最后一步选择(path.pop()
)。
剪枝优化:通过条件提前终止无效分支(见下文剪枝部分)。
应用场景:N 皇后、数独、组合求和、子集问题。
剪枝 是在回溯过程中,通过提前判断某些分支不可能得到有效解,直接跳过这些分支以提升效率。
常见剪枝策略:
去重剪枝:跳过重复选择(需先排序)。
约束剪枝:根据问题条件提前终止无效路径。
def combination_sum(candidates, target):
def backtrack(start, path, remain):
if remain == 0:
res.append(path.copy())
return
for i in range(start, len(candidates)):
# 剪枝1:跳过重复组合(需先排序)
if i > start and candidates[i] == candidates[i-1]:
continue
# 剪枝2:提前终止无效分支
if candidates[i] > remain:
break
path.append(candidates[i])
backtrack(i+1, path, remain - candidates[i]) # 不可重复选同一元素
path.pop()
candidates.sort() # 排序以便剪枝
res = []
backtrack(0, [], target)
return res
print(combination_sum([10, 1, 2, 7, 6, 1, 5], 8))
# 输出: [[1, 1, 6], [1, 2, 5], [1, 7], [2, 6]]
排序预处理:方便去重剪枝(如 [1,2,1]
排序后为 [1,1,2]
)。
剪枝条件设计:
去重剪枝:i > start and candidates[i] == candidates[i-1]
。
约束剪枝:candidates[i] > remain
。
应用场景:组合总和、子集 II、全排列 II 等含重复元素的问题。
类型 | 特点 | 核心操作 | 应用场景 |
---|---|---|---|
基础DFS | 单纯遍历所有节点 | 递归/栈,无状态回溯 | 路径存在性判断、连通性分析 |
回溯DFS | 穷举所有可能解 | 状态记录与撤销(append/pop ) |
组合、排列、子集问题 |
剪枝优化 | 提前终止无效分支 | 条件判断跳过分支 | 含重复元素或约束的优化问题 |
学习建议:
从基础 DFS 理解递归和栈的工作原理。
通过回溯模板掌握状态管理(选择与撤销)。
结合具体问题设计剪枝条件,减少无效计算。
1.LITS 游戏 - 蓝桥云课
5.分糖果 - 蓝桥云课