一、 框架
解决一个回溯问题,实际上就是一个决策树的遍历过程
-
路径:也就是已经做出的选择。
-
选择列表:也就是你当前可以做的选择。
-
结束条件:也就是到达决策树底层,无法再做选择的条件。
代码框架:
result = []
def backtrack(路径, 选择列表):
if 满足结束条件:
result.add(路径)
return
for 选择 in 选择列表:
做选择
backtrack(路径, 选择列表)
撤销选择
核心就是 for 循环里面的递归,在递归调用之前「做选择」,在递归调用之后「撤销选择」
二、举例
1. 全排列问题
n 个不重复的数,全排列共有 n! 个
那么我们当时是怎么穷举全排列的呢?比方说给三个数 [1,2,3],你肯定不会无规律地乱穷举,一般是这样:
先固定第一位为 1,然后第二位可以是 2,那么第三位只能是 3;然后可以把第二位变成 3,第三位就只能是 2 了;然后就只能变化第一位,变成 2,然后再穷举后两位……
其实这就是回溯算法,我们高中无师自通就会用,或者有的同学直接画出如下这棵回溯树:
只要从根遍历这棵树,记录路径上的数字,其实就是所有的全排列。我们不妨把这棵树称为回溯算法的「决策树」
每个节点上其实都在做决策
定义的 backtrack 函数其实就像一个指针,在这棵树上游走,同时要正确维护每个节点的属性,每当走到树的底层,其「路径」就是一个全排列。
如何遍历一棵树
void traverse(TreeNode root) {
for (TreeNode child : root.childern)
# 前序遍历需要的操作
traverse(child);
# 后序遍历需要的操作
}
前序遍历的代码在进入某一个节点之前的那个时间点执行,后序遍历代码在离开某个节点之后的那个时间点执行
「路径」和「选择」是每个节点的属性,函数在树上游走要正确维护节点的属性,那么就要在这两个特殊时间点搞点动作:
for 选择 in 选择列表:
# 做选择
将该选择从选择列表移除
路径.add(选择)
backtrack(路径, 选择列表)
# 撤销选择
路径.remove(选择)
将该选择再加入选择列表
我们只要在递归之前做出选择,在递归之后撤销刚才的选择,就能正确得到每个节点的选择列表和路径。
代码实现 python
def funmain(numlist):
li = []
backtrack(numlist,li)
# 路径在track中
# 选择列表 目前不在track 中的数字
# 结束条件 track 中包含numlist中的所有数据
res = []
def backtrack(numlist,track):
# 结束条件
if(len(track)==len(numlist)):
print(track)
return
for i in range(len(numlist)):
if numlist[i] in track:
continue
track.append(numlist[i])
backtrack(numlist,track)
track.pop();
num = [1,2,3]
funmain(num)
end