回溯算法总结(java)

回溯算法

  • 回溯法按深度优先策略搜索问题的解空间树。首先从根节点出发搜索解空间树,当算法搜索至解空间树的某一节点时,先利用剪枝函数判断该节点是否可行(即能得到问题的解)。如果不可行,则跳过对该节点为根的子树的搜索,逐层向其祖先节点回溯;否则,进入该子树,继续按深度优先策略搜索。
  • 回溯法的基本行为是搜索,搜索过程使用剪枝函数来为了避免无效的搜索。剪枝函数包括两类: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;
    }
}

其他

递归与迭代
  • 计算机解决问题其实没有任何奇技淫巧,它唯一的解决办法就是穷举,穷举所有可能性。算法设计无非就是先思考“如何穷举”,然后再追求“如何聪明地穷举”。其中穷举的代码形式上有两种:递归和迭代
  • 重复调用函数自身实现循环称为递归。递归是用栈机制实现的,每深入一层,都要占去一块栈数据区域,所以有可能导致堆栈溢出的错误。思想简单,代码简洁,将问题交给计算机
  • 利用变量的原值推出新值称为迭代,或着说迭代是函数内某段代码实现循环。由于递归容易导致堆栈溢出,并且递归比迭代效率要低,所以尽量使用迭代
  • 递归是一个树结构,迭代是一个环结构
递归与递推
  • 递归是自顶向下(对应函数入栈),又自底向上(对应函数出栈)。可以在自顶向下的时候进行操作,也可以在自底向上的时候进行操作。
  • 递推是自底向上
两种实现形式
  • 状态记录回溯
  • 交换回溯

你可能感兴趣的:(算法,回溯算法)