回溯算法
回溯算法通常用于搜索解空间。这样的解空间往往是组合爆炸式增长的,比如排列问题就是一个典型示例。
给定一个数字 ,打印出区间 中所有数字的排列。
对于 ,期望输出:
1 2 3 4 5 6
| 1,2,3 1,3,2 2,1,3 2,3,1 3,2,1 3,1,2
|
在这种情况下,我们需要使用回溯来解决问题。回溯会在每次做出决策、选择一个候选项扩展时保存一个“检查点”。
下面给出回溯算法的模板:
1 2 3 4 5 6 7 8 9 10 11 12 13
| public void backtracking(List<Candidate> candidates, List<Candidate> partialSolution) { if (candidates.isEmpty()) { } else { for (Candidate cand : candidates) { backtracking(candidates, partialSolution); } } }
|
下面对排列问题做一个部分“手动演练”:
1. 从 candidates 中选出 1,并移除它
2. 从剩余中选出 2,并移除它
3. 再选出 3,并移除它
4. 此时 candidates 为空,跳出,记录排列 [1,2,3]
5. 把 3 加回 candidates,但此时已无其他候选
6. 把 2 加回 candidates,移动到下一个候选 3
7. 从 candidates 中选出 3,并移除它
8. 再选出 2,并移除它
9. candidates 为空,跳出,记录排列 [1,3,2]
10. 依此类推…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| List<List<Integer>> permutations = new LinkedList<>();
public void backtrack(int n, boolean[] visited, List<Integer> solution) { if (solution.size() == n) { permutations.add(new LinkedList<>(solution)); return; } for (int i = 1; i <= n; i++) { if (!visited[i]) { solution.add(i); visited[i] = true; backtrack(n, visited, solution); visited[i] = false; solution.remove(solution.size() - 1); } } }
|
显然,这里的时间复杂度并不是线性的,实际上与叶子节点数量一致,为 。