什么是回溯法

回溯法也可以叫做回溯搜索法,它是一种搜索的方式



回溯是递归的副产品,只要有递归就会有回溯

在以下讲解中,回溯函数也就是递归函数,指的都是一个函数

回溯法的效率

回溯法的性能如何呢,这里要和大家说清楚了,虽然回溯法很难,很不好理解,但是回溯法并不是什么高效的算法

因为回溯的本质是穷举,穷举所有可能,然后选出我们想要的答案,如果想让回溯法高效一些,可以加一些剪枝的操作,但也改不了回溯法就是穷举的本质

那么既然回溯法并不高效为什么还要用它呢?

因为没得选,一些问题能暴力搜出来就不错了,撑死了再剪枝一下,还没有更高效的解法

那么都是什么问题,只能暴力搜索

回溯法解决的问题

回溯法,一般可以解决如下几种问题:

  • 组合问题:N个数里面按一定规则找出k个数的集合
  • 排列问题:N个数按一定规则全排列,有几种排列方式
  • 切割问题:一个字符串按一定规则有几种切割方式
  • 子集问题:一个N个数的集合里有多少符合条件的子集
  • 棋盘问题:N皇后,解数独等等


另外,这里强调一下什么是组合,什么是排列?

组合是不强调元素顺序,排列是强调元素顺序 ,即组合无序,排列有序

如何理解回溯法

回溯法解决的问题都可以抽象为树形结构,是的,我指的是所有回溯法的问题都可以抽象为树形结构

因为回溯法解决的都是在集合中递归查找子集,集合的大小就构成了树的宽度,递归的深度,都构成的树的深度

递归就要有终止条件,所以必然是一颗高度有限的树(N叉树)

回溯法模板

在讲二叉树的递归中我们说了递归三部曲,这里我再给大家列出回溯三部曲

  • 回溯函数模板返回值以及参数

    • 在回溯算法中,我的习惯是函数起名字为backtracking,回溯算法中函数返回值一般为void
    • 再来看一下参数,因为回溯算法需要的参数可不像二叉树递归的时候那么容易一次性确定下来,所以一般是先写逻辑,然后需要什么参数就填什么参数
  • 回溯函数终止条件

    • 既然是树形结构,那么我们在讲解二叉树的递归的时候,就知道遍历树形结构一定要有终止条件;所以回溯也有要终止条件
    • 什么时候达到了终止条件,树中就可以看出,一般来说搜到叶子节点了,也就找到了满足条件的一条答案,把这个答案存放起来,并结束本层递归
      1. if (终止条件) {
      2. 存放结果;
      3. return;
      4. }
  • 回溯搜索的遍历过程

    • 在上面我们提到了,回溯法一般是在集合中递归搜索集合的大小构成了树的宽度递归的深度构成的树的深度

回溯概览 - 图1
注意图中,我特意举例集合大小和孩子的数量是相等的!

  1. for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
  2. 处理节点;
  3. backtracking(路径,选择列表); // 递归
  4. 回溯,撤销处理结果
  5. }

for循环就是遍历集合区间,可以理解一个节点有多少个孩子,这个for循环就执行多少次

backtracking这里自己调用自己,实现递归

大家可以从图中看出,for循环可以理解是横向遍历,backtracking(递归)就是纵向遍历,这样就把这棵树全遍历完了,一般来说,搜索叶子节点就是找的其中一个结果了

分析完过程,回溯算法模板框架如下:

  1. void backtracking(参数) {
  2. if (终止条件) {
  3. 存放结果;
  4. return;
  5. }
  6. for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
  7. 处理节点;
  8. backtracking(路径,选择列表); // 递归
  9. 回溯,撤销处理结果
  10. }
  11. }

总结

本篇我们讲解了,什么是回溯算法,知道了回溯和递归是相辅相成的

接着提到了回溯法的效率,回溯法其实就是暴力查找,并不是什么高效的算法

最后我们讲到回溯法解决的问题都可以抽象为树形结构(N叉树),并给出了回溯法的模板