回溯算法
为什么回溯算法很重要
回溯通过逐步构建解并放弃不可能产生有效解的部分解,系统地探索所有可能性:
- 组合问题:生成所有排列/组合
- 约束满足:数独、N 皇后、填字游戏
- 路径搜索:迷宫求解、图遍历
- 优化:从众多可能性中找到最优解
实际影响:
- N 皇后用回溯:N=8 时 2 秒
- 暴力检查所有位置:N=8 时 4 小时
- 加速 7,200 倍
核心概念
回溯模板
void backtrack(state, parameters) {
if (isSolution(state)) {
recordSolution(state);
return;
}
for (choice in generateChoices(state)) {
if (isValid(choice)) {
makeChoice(choice);
backtrack(state, parameters);
undoChoice(choice); // 回溯
}
}
}
回溯 vs 递归
| 方面 | 回溯 | 递归 |
|---|---|---|
| 搜索空间 | 系统性探索 | 分治法 |
| 剪枝 | 激进( 提前终止) | 通常无剪枝 |
| 用例 | 约束满足 | 问题分解 |
| 状态管理 | 做出/撤销选择 | 无需状态回滚 |
深入理解
全排列
生成所有可能的排列:
public List<List<Integer>> permute(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
backtrack(result, new ArrayList<>(), new boolean[nums.length], nums);
return result;
}
private void backtrack(List<List<Integer>> result, List<Integer> current,
boolean[] used, int[] nums) {
if (current.size() == nums.length) {
result.add(new ArrayList<>(current));
return;
}
for (int i = 0; i < nums.length; i++) {
if (used[i]) continue;
current.add(nums[i]);
used[i] = true;
backtrack(result, current, used, nums);
current.remove(current.size() - 1); // 回溯
used[i] = false;
}
}