回溯是一种系统性的算法范式,它通过探索所有可能的解决方案来解决问题。它通过逐步构建解决方案的候选者,并在确定候选者不可能导向有效解决方案时放弃该候选者(“回溯”)。本页面解释了回溯算法的核心概念、实现模式及其应用。
有关动态规划的信息,它通常可以解决一些可以采用回溯方法解决的问题,但效率特征不同,请参阅动态规划初步探索。
回溯算法可以看作是对表示所有可能解决方案的状态空间的深度优先搜索。算法分步做出了一系列选择,试图逐步构建解决方案。当它确定当前选择无法导向有效解决方案时,它会“回溯”到前一个状态并做出不同的选择。
来源:[docs/chapter_backtracking/backtracking_algorithm.md:1-5]
回溯的本质在于“尝试”与“回溯”的过程
这可以通过一个简单的二叉树遍历问题来说明:在二叉树中查找值为 7 的节点的路径。
当我们访问节点 7 时,我们会记录路径并继续探索。当我们到达叶节点或完成一个分支的探索后,我们会通过从路径中删除最后一个节点来进行回溯,并尝试另一条分支。
来源:[docs/chapter_backtracking/backtracking_algorithm.md:18-62]
剪枝是一种通过避免不必要的探索某些分支来提高效率的技术。当我们认识到某些分支根据问题约束不可能导向有效解决方案时,我们可以对其进行剪枝。
例如,如果我们正在寻找值为 7 的节点路径,但有一个约束条件是路径不能包含值为 3 的节点,那么我们可以立即剪掉任何以节点 3 开始的分支。
来源:[docs/chapter_backtracking/backtracking_algorithm.md:74-90]
大多数回溯问题都可以使用遵循此模式的标准模板来解决
回溯算法中的核心函数包括
is_solution(state):确定当前状态是否为有效解决方案record_solution(state, result):记录解决方案is_valid(state, choice):检查在当前状态下某个选择是否有效make_choice(state, choice):通过做出选择来更新状态undo_choice(state, choice):将状态恢复到做出选择之前的状态来源:[docs/chapter_backtracking/backtracking_algorithm.md:92-404]
通用的回溯框架可以实现如下
backtrack(state, choices, result):
# Check if current state is a solution
if is_solution(state):
record_solution(state, result)
return
# Try each possible choice
for choice in choices:
# Pruning: only consider valid choices
if is_valid(state, choice):
# Try this choice
make_choice(state, choice)
# Recursive call
backtrack(state, choices, result)
# Backtrack
undo_choice(state, choice)
此模板可用于解决各种回溯问题。
来源:[docs/chapter_backtracking/backtracking_algorithm.md:98-404]
回溯算法存在显著的时间和空间复杂度限制
对于大型或复杂的问题,如果不进行优化,回溯可能会变得不切实际。
来源:[docs/chapter_backtracking/backtracking_algorithm.md:469-482]
回溯的两种主要优化技术
来源:[docs/chapter_backtracking/backtracking_algorithm.md:477-482]
找出给定元素集的所有可能排列。关键考虑因素包括
来源:[docs/chapter_backtracking/permutations_problem.md:1-95]
找出所有和为目标值的元素子集。挑战包括
来源:[docs/chapter_backtracking/subset_sum_problem.md:1-95]
将 N 个皇后放置在 N×N 的棋盘上,使得任意两个皇后都不能互相攻击。关键方面
来源:[docs/chapter_backtracking/n_queens_problem.md:1-53]
回溯与其他搜索算法在重要方面有所不同
| 算法 | 方法 | 优化 | 应用程序 |
|---|---|---|---|
| 回溯 | 尝试所有可能性并通过系统剪枝 | 剪枝那些无法导向有效解决方案的分支 | 找出所有满足约束的解决方案 |
| 动态规划 | 将问题分解为重叠的子问题 | 通过存储结果避免重复计算子问题 | 具有重叠子问题的优化问题 |
| 贪心算法 | 在每一步做出局部最优选择 | 如果选择导向次优解决方案,则不回溯 | 具有贪心选择性质的问题 |
| 分治法 | 分解为独立的子问题 | 组合子问题解决方案 | 没有重叠子问题的特征 |
来源:[docs/chapter_dynamic_programming/dp_problem_features.md:3-9], [docs/chapter_dynamic_programming/intro_to_dynamic_programming.md:21-25]
理解标准术语有助于分析回溯问题
| 术语 | 定义 | 示例 |
|---|---|---|
| 解决方案 | 满足所有问题约束的答案 | N 个皇后的一个有效布局 |
| 约束 | 限制有效解决方案的条件 | 皇后不能相互攻击 |
| 状态管理 | 表示搜索的当前进度 | 当前的棋盘部分布局 |
| 尝试 | 探索一个选择的过程 | 在位置 (i,j) 放置皇后 |
| 回溯 | 撤销一个选择以尝试其他选项 | 当放置皇后导致冲突时将其移除 |
| 剪枝 | 跳过无法导向解决方案的分支 | 不探索已放置皇后的列 |
来源:[docs/chapter_backtracking/backtracking_algorithm.md:451-464]
回溯是一种强大的算法范式,用于在避免不必要探索的同时,穷举搜索解决方案空间。它对于约束满足问题、搜索问题和组合优化问题特别有效,当应用了适当的剪枝技术时。尽管可能具有指数级的时间复杂度,但对于许多需要找到所有可能解决方案的问题,回溯仍然是最有效的方法。