重点:
(1)深搜:递归和栈实现,先中后序遍历;广搜:队列实现,层次遍历;
(2)回溯的本质是穷举,即暴力搜索(BFS和DFS),所以效率不高;
(3)回溯法解决问题:组合问题,切割问题,子集问题,排列问题,棋盘问题;
(4)for循环分布:背包内物品外组合,背包外物品内排列:
(5)回溯法抽象为一棵树:集合的大小构成树的宽度,递归的终止条件构成深度;
(6)回溯模板:for实现横向遍历,backtracking实现纵向遍历;
(7)回溯法三部曲:确定函数参数及返回条件void;终止条件;单层递归逻辑(撤销回溯);
#什么是回溯法
(1)回溯法即回溯搜索,即深搜,是一种搜索的方式,实现形式是递归;
(2)深搜实现形式有递归和栈,对应先中后序遍历。广搜用队列实现,对应层次遍历;
#回溯法的效率
(1)回溯法的本质是穷举,即暴力搜索,所以效率不高;
(2)但由于很多问题只有用穷举才能解决,所以没有更高效的算法;
那么哪些问题只能用穷举解决呢?
#回溯法解决的问题
回溯法一般解决如下问题:
(1)组合问题:N和数里面按一定规则找出k个数的集合;
(2)切割问题:字符串切割方式有多少种;
(3)子集问题:N个数的集合里有多少符合条件的子集;
(4)排列问题:N个数全排列,多少种方式;
(5)棋盘问题:N皇后,解数独等;
对于上面这些问题,只能通过穷举解决。
并且需要注意,排列和组合的区别。同时需要注意,for循环的内层外层分别是什么,决定了该问题是排列还是组合。
#如何理解回溯法
(1)回溯法可抽象为树形结构:集合的大小构成树的宽度,递归的深度构成树的深度,递归要有终止条件;
#回溯法模板
由递归三部曲延申出回溯三部曲:
递归三部曲:
(1)递归函数和返回值;(2)确定终止条件;(3)确定单层递归逻辑;
回溯三部曲:
(1)回溯函数模板返回值及参数:
一般起名为backtracking,返回值一般void。参数一般在写的过程中,需要什么就添加什么;
void backtracking(参数)
(2)终止条件
回溯要有终止条件。因为深度是有限的。
当达到了终止条件,一般就找到了满足条件的答案,记录答案,结束本层递归。
if(终止条件){
存放结果;
return;
}
(3)回溯搜索的遍历过程
一般在集合中递归搜索,集合的大小构成树的宽度,递归深度构成树的深度。
如下图:
遍历代码如下:
for(选择:本层集合中元素(树中节点孩子数量大小就是集合的大小)){
处理节点;
backtracking(路径,选择列表);//递归
回溯,撤销处理结果;
}
(1)for循环遍历集合区间,可以理解每个节点有多少个孩子,这个for就执行多少次;
(2)for实现横向遍历,递归实现纵向遍历,这样就遍历完了;
总结模板:
void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}