大家好,欢迎来到IT知识分享网。
回溯算法
- 回溯法按深度优先策略搜索问题的解空间树。首先从根节点出发搜索解空间树,当算法搜索至解空间树的某一节点时,先利用剪枝函数判断该节点是否可行(即能得到问题的解)。如果不可行,则跳过对该节点为根的子树的搜索,逐层向其祖先节点回溯;否则,进入该子树,继续按深度优先策略搜索。
- 回溯法的基本行为是搜索,搜索过程使用剪枝函数来为了避免无效的搜索。剪枝函数包括两类:1. 使用约束函数,剪去不满足约束条件的路径;2.使用限界函数,剪去不能得到最优解的路径。
- 回溯算法说白了就是穷举法。不过回溯算法使用剪枝函数,剪去一些不可能到达 最终状态(即答案状态)的节点,从而减少状态空间树节点的生成,提高了效率。
- 回溯算法一般都采用递归实现。回溯利用递归的性质,从问题的起始点出发,不断地进行尝试,回头一步甚至多步再做选择,直到最终抵达终点。
回溯解题模板
function backtrack(n, used) {
// 判断输入或者状态是否非法
if (input/state is invalid) {
return;
}
// 判读递归是否应当结束,满足结束条件就返回结果
if (match condition) {
return some value;
}
// 遍历当前所有可能出现的情况,并尝试每一种情况
for (all possible cases) {
// 如果上一步尝试会影响下一步尝试,需要写入状态
used.push(case)
// 递归进行下一步尝试,搜索该子树
result = backtrack(n + 1, used)
// 在这种情况下已经尝试完毕,重置状态,以便于下面的回溯尝试
used.pop(case)
}
}
经典问题
leetcode17. 电话号码的字母组合
class Solution {
private List<String> res = new ArrayList<>();
private final String[] letterMap = new String[]{
"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
public List<String> letterCombinations(String digits) {
if (digits.length() == 0) {
return res;
}
findCombination(digits, 0, "");
return res;
}
private void findCombination(String digits, int index, String s){
if(index == digits.length()){
res.add(s);
return;
}
Character c = digits.charAt(index);
String letters = letterMap[c - '0'];
for(int i = 0 ; i < letters.length() ; i ++){
findCombination(digits, index+1, s + letters.charAt(i));
}
return;
}
}
leetcode46. 全排列
public class Solution {
private List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> permute(int[] nums) {
if (nums.length == 0) {
return res;
}
boolean[] used = new boolean[nums.length];
List<Integer> list = new ArrayList<>();
backtrack(nums, 0, list, used);
return res;
}
public void backtrack(int[] nums, int index, List<Integer> list, boolean[] used) {
if (index == nums.length) {
res.add(new ArrayList<>(list));
return;
}
for (int i = 0; i < nums.length; i++) {
if (!used[i]) {
list.add(nums[i]);
used[i] = true;
backtrack(nums, index + 1, list, used);
list.remove(list.size() - 1);
used[i] = false;
}
}
}
}
leetcode51. N皇后
class Solution {
public List<List<String>> res = new ArrayList<>();
public List<List<String>> solveNQueens(int n) {
int[] cols = new int[n];
for (int i=0; i<n; i++) {
cols[i] = -1;
}
backtrack(0, n, cols);
return res;
}
public void backtrack(int index, int n, int[] cols) {
if (index == n) {
res.add(locToList(cols));
return;
}
for (int i=0; i<n; i++) {
cols[index] = i;
if (isSafe(cols, index)) {
backtrack(index+1, n, cols);
}
cols[index] = -1;
}
}
public boolean isSafe(int[] cols, int index) {
for (int i=0; i<index; i++) {
if (cols[i] == cols[index] || i+cols[i] == index+cols[index] || i-cols[i] == index-cols[index]) {
return false;
}
}
return true;
}
public List<String> locToList(int[] cols) {
ArrayList<String> list = new ArrayList<>();
for (int i=0; i<cols.length; i++) {
StringBuilder sb = new StringBuilder();
for (int j=0; j<cols[i]; j++){
sb.append(".");
}
sb.append("Q");
for (int j=cols[i]+1; j<cols.length; j++){
sb.append(".");
}
list.add(sb.toString());
}
return list;
}
}
其他
递归与迭代
- 计算机解决问题其实没有任何奇技淫巧,它唯一的解决办法就是穷举,穷举所有可能性。算法设计无非就是先思考“如何穷举”,然后再追求“如何聪明地穷举”。其中穷举的代码形式上有两种:递归和迭代
- 重复调用函数自身实现循环称为递归。递归是用栈机制实现的,每深入一层,都要占去一块栈数据区域,所以有可能导致堆栈溢出的错误。思想简单,代码简洁,将问题交给计算机
- 利用变量的原值推出新值称为迭代,或着说迭代是函数内某段代码实现循环。由于递归容易导致堆栈溢出,并且递归比迭代效率要低,所以尽量使用迭代
- 递归是一个树结构,迭代是一个环结构
递归与递推
- 递归是自顶向下(对应函数入栈),又自底向上(对应函数出栈)。可以在自顶向下的时候进行操作,也可以在自底向上的时候进行操作。
- 递推是自底向上
两种实现形式
- 状态记录回溯
- 交换回溯
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/10192.html