已发布·持续改进
Algorithm
回溯法逐步构造解,一旦违反约束就撤销选择并返回上一步,借助剪枝高效求解排列、组合、N 皇后和数独等问题。
回溯法一步一步地填充答案,以深度优先的方式搜索状态空间树;一旦当前的选择违反约束,就退回上一步尝试其他选项。代码由三个动作反复组成:选择一个候选,递归向下探索,返回时撤销刚才的选择。
与先生成所有完整候选再检查的暴力枚举不同,回溯法会在中途剪掉没有希望的分支,往往能以少几个数量级的尝试解决同一问题。它是枚举排列、组合以及求解 N 皇后、数独、图着色等约束满足问题的标准方法,也是正则表达式引擎和 SAT 求解器的基础,因此在算法面试和实际工程中都很常见。
建议先用子集、排列这类不需要剪枝的枚举问题掌握“选择、探索、撤销”的模板,再通过 N 皇后和数独练习约束检查与剪枝。之后亲自统计访问的节点数,体会剪枝的效果,并把同样的问题与动态规划对比,弄清什么情况下该选哪种方法。
以空状态为根、每次选择为边,所有候选都成为树上的路径,回溯法按深度优先遍历这棵树。
把候选加入部分解,递归向下,返回时再移除,从而让所有分支共用同一个列表。
当前部分解不可能得到解时,跳过其下方的整棵子树,使指数级的搜索空间在实际中大幅缩小。
用集合或数组记录已占用的列、对角线、行或宫格,就能在常数时间内判断新的选择是否违反规则。
permutations 用 used 数组排除已选元素,按“选择、探索、撤销”生成全部排列,完成的路径用 path[:] 复制后保存。n_queens 用集合记录列和两条对角线(row - c、row + c),立即跳过受攻击的格子并统计解的个数。运行 python backtracking.py 会输出 [1, 2, 3] 的 6 个排列和 8 皇后的解数 92。
backtracking.py
def permutations(items):
result, path = [], []
used = [False] * len(items)
def backtrack():
if len(path) == len(items):
result.append(path[:]) # store a copy of the full path
return
for i, x in enumerate(items):
if used[i]:
continue
used[i] = True # choose
path.append(x)
backtrack() # explore
path.pop() # unchoose
used[i] = False
backtrack()
return result
def n_queens(n):
cols, diag, anti = set(), set(), set()
def place(row):
if row == n:
return 1
count = 0
for c in range(n):
if c in cols or row - c in diag or row + c in anti:
continue # prune: square is attacked
cols.add(c); diag.add(row - c); anti.add(row + c)
count += place(row + 1)
cols.remove(c); diag.remove(row - c); anti.remove(row + c)
return count
return place(0)
print(permutations([1, 2, 3]))
print(n_queens(8)) # 92
python backtracking.py共六章,带你从安装一步步了解 回溯法 的核心概念。
在这里提问、分享经验,交流关于 回溯法 的看法。
还没有讨论。来发起第一个吧。
0 条评论
登录 · 登录后即可发表评论。
来发表第一条评论吧。