Keyboard shortcuts

Press or to navigate between chapters

Press ? to show this help

Press Esc to hide this help

回溯与搜索树

复习

  • 深度优先搜索:一路走到底再回头
  • 递归:函数调用自身
  • 设计递归:拆小问题、确保收敛

TL;DR

  • 回溯把每个选择展开成一棵“搜索树”
  • 沿一条路尝试,失败就撤销选择、回到上一步
  • 它天然对应 DFS 和递归
  • 回溯能求出所有解,但代价可能是指数级

正文

  有些问题,需要尝试所有可能的组合,比如“列出所有排列”“摆放皇后”“选出所有子集”。暴力地试一遍所有可能,正是回溯(backtracking)擅长的领域。

把选择展开成一棵树

  回溯把求解过程看成一棵搜索树

  • 树的根,是问题的起点
  • 每做一次选择,就往下走一层
  • 每个叶子的路径,对应一种候选方案

  于是,求所有解,就变成了“遍历这棵搜索树”。这自然对应深度优先搜索:沿着一条路径深入,走到底就得到一个解(或不合法),然后退回来,换一个选择继续。

尝试,然后撤销

  回溯的精髓,在“撤销”这两个字。

  沿着一条路径往下走时,我们要把当前选择“记入方案”。如果这条路行不通、或者已经得到了一个完整解,就得把刚才的选择撤销,恢复到做选择之前的状态,再去试别的分支。

  这个“做选择 → 深入 → 撤销 → 试下一个”的循环,天然适合用递归实现:递归调用前做选择、调用后撤销选择,调用栈自动帮我们记住每一层的位置。

它能求所有解,但可能很慢

  回溯的优点很明确:只要问题能用“逐步选择”描述,它就能系统地枚举出所有解,包括那些需要复杂条件的问题。

  代价则是:搜索树可能极其庞大。如果每一步都有多个选择、又没有有效的限制,节点数量会随层数指数增长,很快就搜不完。所以,回溯通常必须配合下一章的剪枝,才能在实际问题中派上用场。

思考题 1

回溯算法中,“撤销选择”为什么必不可少?

思考题 2

为什么回溯法天然适合用递归实现?

小结

知识点

  • 回溯把选择过程展开成一棵搜索树
  • 通过 DFS 遍历搜索树求所有解
  • 核心是“做选择—深入—撤销—试下一个”
  • 天然对应递归,但代价可能指数级

参考资料

  1. Wikipedia(zh):回溯法:逐步尝试并回退的搜索方法
  2. Wikipedia(zh):深度优先搜索:回溯遍历搜索树的基础

思考题答案(仅供参考)

思考题 1

  因为回溯要在一条路径失败后,回到上一步去尝试别的分支。若不撤销之前的选择,当前方案就会被“污染”或越积越多,导致后续尝试基于错误状态进行。撤销让状态恢复干净,才能正确探索其他分支。

思考题 2

  因为回溯的过程是“做选择 → 递归深入 → 撤销”,而递归调用栈天然保存了每一层的位置和状态,返回时正好对应“回到上一步”。用递归表达,逻辑与搜索树的深入、回退一一对应。

协议

  本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。

封面图

设计师 | 南国微雪