一、课程目标
- 搜索的概念
- 回溯的作用
- 算法的要素
- 案例
- 基本模型
二、目标详解
1、搜索的概念
我们学过枚举算法:穷举所有的状态,进行判定,获得解。
然而有些问题的状态比较特殊,是由多个维度组成,比如百钱买百鸡,状态是公鸡数x、母鸡数y、小鸡数z的组合,那么这些状态如何穷举呢?
如果维度不多,可以用多重循环实现,当维度比较多或者个数不确定的时候,就需要用到搜索算法了。
搜索算法利用递归函数来获得所有的维度组合,进而筛选出需要的状态,进行判定求解。
2、回溯
搜索时,当某个状态明显不满足,就可以退回上一个状态,继续选择别的方向。这种思想就称为回溯。
假设每一步的状态为Xi,某一个解(由状态组成的路径)为X1 .. Xn。
使用回溯法的前提:
- 如果X1..Xn成立,则X1..Xn-1肯定也成立。
- 如果X1..Xn不成立,则X1..Xn+1肯定也不成立。
3、搜索算法的要素
搜索算法往往采用递归函数的方式来实现搜索+回溯,实际如果熟练掌握算法的要素的话,也可以采用栈、死循环等方式来实现。
3.1 退出
如果把搜索算法看成很多层选择的话,那么当每一层的选择(路径)都走完以后,就可以退出了。
退出条件:
- 栈结构实现:栈里没有节点,就退出。
- 递归函数实现:第一层函数调用结束,自然退出。相当于栈里没有节点了。
- 死循环实现:从第一层开始搜索,每层搜索结束,回退一层,到第0层就表示退出。
3.2 求解
最后一层有可行路径,表示有了一个解(从第一层到最后一层的路径)。
一般每一层有可行路径后,即进入下一层搜索,如果下一层超过了层数就表示有解。
3.3 选择
每层根据可选的路径进行选择,根据某种判定条件检查路径是否可行,如果可行就进入下一层搜索。
如果所有选择都不可行(表示无解)则回退到上一层。
3.4 回溯
- 当进入下一层前,记录本层的选择,一方面可能下一层的判定要用到,另一方面求解时用到。
- 当求解完成回退,或某层无解回退时,需要把上一层的记录清除,以便上一层再往下选择。
4、案例
以1-3的全排列为例子,如下图:
- 第一轮:
- 第一层1..3中选择了1
- 然后调用第二层1..3中选择了2(1已经被第一层选择)
- 然后调用第三层1..3中选择了3(1,2已经被选)
- 解为1 2 3,然后回溯到第三层
- 第三层已经没有可选,继续回溯到第二层
- 第二层还有3可以选择,因此作为第二轮的开始
- 第二轮:
- 第一层不变,从第二轮开始
- 第二轮选择3
- 然后调用第三层1..3中选择2(1, 3已经被选)
- 解为1 3 2,然后回溯到第三层
- 第三层已经没有可选,继续回溯到第二层
- 第二层已经没有可选,继续回溯到第一层
- 第一层还有2、3可选,因此作为第三轮的开始
- 第三轮…依次类推
5、基本模型
void search() {
if(有解) {
处理解
返回;
}
for(本层可行的选择) {
状态变化;
search();//往下递归搜索
回溯;
}
}
四、扩展理解
1、递归与搜索
一般搜索会使用递归进行回溯,要注意,搜索的重点是回溯而不是递归。
搜索的每一次递归迭代代表一层方向的选择,一般是用循环来实现。比如1-3全排列里面,第一次递归代表第一个位置的选择(有3个),然后递归下去,代表第一层选择确定的情况下,第二层的选择(也有3个),以此类推。
对应的,当某层选择不可行(或已到最后)时,回溯到上一层,需要清除掉上一层的记录。
对比搜索递归与分治递归,会发现区别就在于有没有回溯。
为什么要回溯?就是因为上一层还要再看看别的方向选择。
2、解空间树
使用回溯法解题时,如果把每层搜索的状态按照树的结构连接起来,就会组成一个解空间树。树的一条边(从根到叶子)就是一个解,常见的解空间树一般可分为为排列树和子集树。
2.1 排列树
当问题为寻求n个元素的某种排列时,解空间树称为排列树,排列树通常有n!个叶子节点(第一层有n个选择,第二层n-1,依次类推)。
2.2 子集树
当问题为在n个元素的集合种找某种子集时,解空间树称为子集树。子集树实际就是满二叉树,叶子节点为为2^n个。