Open mengxh1990 opened 4 years ago
回溯算法是个思想,回溯法的实现方法有两种:递归和递推(也称迭代,非递归)。一般来说,一个问题两种方法都可以实现,只是在算法效率和设计复杂度上有区别。 类比于图深度遍历的递归实现和非递归(递推)实现
https://blog.csdn.net/sinat_27908213/article/details/80599460?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task 对于回溯法来说,每次递归调用,很重要的一点是把每次递归的不同信息传递给递归调用的函数。而这里最重要的要传递给递归调用函数的信息,就是把上一步做过的选择排除,避免重复和无限递归。另外还有一个信息必须传递给递归函数,就是进行了每一步选择后,暂时还没构成完整的解,这个时候前面所有选择的汇总也要传递进去。而且一般情况下,都是能从传递给递归函数的参数处,得到结束条件的。
回溯算法是个思想,一般用递归实现,递归函数设计: 1、必须要有一个临时变量(初始时可以就直接传递一个字面量或者常量进去)传递不完整的解,表示截至当前解的情况,因为每一步选择后,暂时还没构成完整的解,这个时候这个选择的不完整解,也要想办法传递给递归函数。也就是,把每次递归的不同情况传递给递归调用的函数。【每一步的解的汇总信息传递下去】表示临时路径。 2、可以有一个全局变量,用来存储完整的每个解,一般是个集合容器(也不一定要有这样一个变量,因为每次符合结束条件,不完整解就是完整解了,直接打印即可)。【存储完整的解空间,也可以用类的属性】 3、最重要的一点,一定要在参数设计中,可以得到结束条件。一个选择是可以传递一个循环变量,也许是数组的长度,也许是数量,索引等等。【结束条件】 4、要保证递归函数返回后,状态可以恢复到递归前,以此达到真正回溯。【恢复状态:下一次递归前,把当前这一步的选择清除或者还原】 回溯的意思就是要回去,递归函数自动保证了回去,但是我们设置的其他变量如果有必要的话也必须要回到原位。
通过循环变量或者临时路径进行扩展,看情况,一般循环变量扩展时增加1,临时路径扩展时尾部增加一步。 做选择、扩展递归下一步、撤销选择。
两大类: 1,解空间树的深度或者路径长度已经明确告知为N,又分为子集树和排列树两种 2,解空间树的深度或者路径长度没有明确告知,求解后才知道
https://blog.csdn.net/weiyuefei/article/details/79316653 回溯法思路的简单描述是:把问题的解空间转化成了图或者树的结构表示,然后使用深度优先搜索策略进行遍历,遍历的过程中记录和寻找所有可行解或者最优解。 回溯法按深度优先策略搜索问题的解空间树。首先从根节点出发搜索解空间树,当算法搜索至解空间树的某一节点时,先利用剪枝函数判断该节点是否可行(即能得到问题的解)。如果不可行,则跳过对该节点为根的子树的搜索,逐层向其祖先节点回溯;否则,进入该子树,继续按深度优先策略搜索。 回溯法的基本行为是搜索,搜索过程使用剪枝函数来为了避免无效的搜索。剪枝函数包括两类:1. 使用约束函数,剪去不满足约束条件的路径;2.使用限界函数,剪去不能得到最优解的路径。 问题的关键在于如何定义问题的解空间,转化成树(即解空间树)。解空间树分为两种:子集树和排列树。两种在算法结构和思路上大体相同。
补充链接: https://www.jiqizhixin.com/graph/technologies/55e8bc82-9c28-4900-bf08-3f6f05a62d60 https://cs.lmu.edu/~ray/notes/backtracking/ https://www.quora.com/q/loveforprogramming/Backtracking-Memoization-Dynamic-Programming https://www.youtube.com/watch?v=gBC_Fd8EE8A
伪代码:
void findSolutions(n, other params) :
if (found a solution) :
solutionsFound = solutionsFound + 1;
displaySolution();
if (solutionsFound >= solutionTarget) :
System.exit(0);
return
for (val = first to last) :
if (isValid(val, n)) :
applyValue(val, n);
findSolutions(n+1, other params);
removeValue(val, n);
public List<List<Node>> rootToLeaf(Node root) {
List<List<Node>> result = new ArrayList<>();
if (root == null) {
return result;
}
LinkedList<Node> path = new LinkedList<>();
dfs(root, result, path);
return result;
}
/*
*1,root为循环变量,不断发生变化,直到结束条件
*2,paths为最终解空间,不断丰富
*3,list为临时变量,表示当前汇总信息,传递下去
*/
private void dfs(Node root, List<List<Node>> paths, LinkedList<Node> list) {
if (root == null) return;
list.add(root);
if (root.left == null && root.right == null) {
paths.add(new ArrayList<>(list));//一定要拷贝一份,因为后面list还要变
list.removeLast();
return;//到达叶子节点后,一定要返回;每次返回前要清除最后一个节点
}
dfs(root.left, paths, list);
dfs(root.right, paths, list);
list.removeLast();//4,恢复状态
}
回溯法总结 0,leetcode的定义,与递归,深度优先搜索的关系 1,活结点,扩展节点,死节点 2,以0/1背包问题做例子 3,子集树,排列数 4,通用解法: 路径,选择列表,做选择,撤销选择 5,leetcode例题 6,再回头看与递归,深度优先搜索的关系,外加与动态规划的关系 7,参考论文
回溯算法
基本描述
leetcode定义 回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就 “回溯” 返回,尝试别的路径。回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为 “回溯点”。许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称。
与递归,DFS关系 回溯算法是个思想,回溯法的实现方法有两种:递归和递推(也称迭代,非递归)。一般来说,一个问题两种方法都可以实现,只是在算法效率和设计复杂度上有区别。 与DFS的整体过程是一致的,都是一条路走到底,不撞南墙不回头。回溯算法是枚举,枚举的方式是深度优先搜索,回溯算法需要在深度优先搜索的同时,把临时路径一直传递下去。 回溯算法三元素
概念及原理
这类问题的解可表示成一个n元组(x0,x1,…,xn-1),求满足约束条件的可行解,或进一步求使目标函数取最大(或最小)值的最优解问题,这类问题一般都可以用回溯法求解。 回溯法通过使用约束函数和限界函数来压缩需要实际生成的状态空间树的结点数,从而大大节省问题求解时间。
解空间树的三种节点
确定了解空间的组织结构后,回溯法从根节点出发,以深度优先的方式搜索整个解空间。这个开始节点成为一个活节点,同时也成为当前的扩展节点。在当前的扩展节点处,搜索向纵深方向移至一个新节点。这个新节点就成为一个新的活节点,并成为当前扩展节点。如果在当前扩展节点处不能再向纵深方向移动,则当前的扩展节点就成为死节点。此时,应往回移动即回溯至最近的一个活节点处,并使这个活节点成为当前的扩展节点。回溯法即以这种方式递归地在解空间中搜索,直到找到所要求的解或者解空间中已无活节点为止。 以0/1背包问题为例
通用解法
一般用递归实现,递归函数设计: 1、必须要有一个临时变量(初始时可以就直接传递一个字面量或者常量进去)传递不完整的解,表示截至当前解的情况,因为每一步选择后,暂时还没构成完整的解,这个时候这个选择的不完整解,也要想办法传递给递归函数。也就是,把每次递归的不同情况传递给递归调用的函数。【每一步的解的汇总信息传递下去】表示临时路径。 2、可以有一个全局变量,用来存储完整的每个解,一般是个集合容器(也不一定要有这样一个变量,因为每次符合结束条件,不完整解就是完整解了,直接打印即可)。【存储完整的解空间,也可以用类的属性】 3、最重要的一点,一定要在参数设计中,可以得到结束条件。一个选择是可以传递一个递增变量,也许是数组的长度,也许是数量,索引等等。【结束条件】 4、要保证递归函数返回后,状态可以恢复到递归前,以此达到真正回溯。【恢复状态:下一次递归前,把当前这一步的选择清除或者还原】 回溯的意思就是要回去,递归函数自动保证了回去,但是我们设置的其他变量如果有必要的话也必须要回到原位。
伪代码:
leetcode回溯算法题解
leetcode例题
416.分割等和子集 转化为[0/1背包问题]:
为了套用模板,写的不够简洁,有更简洁的写法。
254因子的组合 与前一题的不同之处在于,单个解向量的元素个数是不确定的。
51. N皇后问题
1087.字母切换
267.回文排列II
总结
回溯法思路的简单描述是:把问题的解空间转化成了图或者树的结构表示,定义解空间树,然后使用深度优先搜索策略进行遍历,遍历的过程中记录和寻找所有可行解或者最优解。首先从根节点出发搜索解空间树,当算法搜索至解空间树的某一节点时,先利用剪枝函数判断该节点是否可行(即能得到问题的解)。如果不可行,则跳过对该节点为根的子树的搜索,逐层向其祖先节点回溯;否则,进入该子树,继续按深度优先策略搜索。
贪心法和动态规划法都要求问题最优解具有最优子结构特性。贪心法求解还要求设计最优量度标准,但这并非易事,这时候可以用回溯。回溯法是比贪心法和动态规划法更一般的方法。
回溯法
课后练习
37数独 1079活字印刷