算法学习--回溯和剪枝

1. 回溯

1.1 回溯的应用场景

  • 递归代表开启一个分支,如果希望这个分支返回后某些数据恢复到分支开启以前的状态以便重新开始,则需要用到回溯

1.2 典型例题

1.2.1 全排列

public class Q9_全排列 {
    public static void main(String[] args) {
        String s = "231564";
        System.out.println(getPermutation(s));
        System.out.println(getPermutation(s, s.length()-1));
        System.out.println(getPerm(s));
    }

    //回溯写法
    public static ArrayList<String> res = new ArrayList<>();

    public static ArrayList<String> getPerm(String s){
        char[] arr = s.toCharArray();
        //Arrays.sort(arr);
        getPerm(arr, 0);
        return res;
    }

    private static void getPerm(char[] arr, int i) {
        if(i == arr.length){
            res.add(new String(arr));
        }
        for (int j = i; j < arr.length; j++) {
            //把i后面的每个字符换到i这个位置
            swap(arr, i, j);
            //排好每一个i位置,排i+1的位置
            getPerm(arr, i+1);
            //回溯,恢复原样,便于下一个字符放到这个地方
            swap(arr, i, j);
        }
    }

    private static void swap(char[] arr, int i, int j){
        char temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

1.2.2 数独游戏

public class Q8_数独游戏 {
    public static void main(String[] args) {
        int[][] table = {
                {0,0,5,3,0,0,0,0,0},
                {8,0,0,0,0,0,0,2,0},
                {0,7,0,0,1,0,5,0,0},
                {4,0,0,0,0,5,3,0,0},
                {0,1,0,0,7,0,0,0,6},
                {0,0,3,2,0,0,0,8,0},
                {0,6,0,5,0,0,0,0,9},
                {0,0,4,0,0,0,0,3,0},
                {0,0,0,0,0,9,7,0,0}
        };
        dfs(table, 0, 0);
    }

    public static void dfs(int[][] table, int x, int y){
        if(x==table.length){
            for (int[] ints : table) {
                System.out.println(Arrays.toString(ints));
            }
            System.exit(0);
        }
        //当前位置没有填数据
        if(table[x][y] == 0){
            //从1-9选合法的数据
            for (int i = 1; i < 10; i++) {
                //检查
                if(check(table, x, y, i)){ //可以填
                    table[x][y] = i;
                    //处理下一个位置
                    dfs(table, x+(y+1)/9, (y+1)%9);
                }
            }
            table[x][y] = 0; //需要回溯
        }else{
            //有数据,处理下一个位置
            dfs(table, x+(y+1)/9, (y+1)%9);
        }
    }

    //判断某个点能不能填某个数
    private static boolean check(int[][] table, int x, int y, int i) {
        for (int j = 0; j < table[0].length; j++) {
            if(table[x][j] == i || table[j][y] == i){
                return false;
            }
        }
        for (int j = (x/3)*3; j < (x/3+1)*3; j++) {
            for (int k = (y/3)*3; k < (y/3+1)*3; k++) {
                if(table[x][y] == i){
                    return false;
                }
            }
        }
        return true;
    }
}

1.2.3 部分和

/*
一个数组中找出若干个数,使他们的和为给定的数
* */
public class Q12_部分和 {
    public static void main(String[] args) {
        int[] arr = {1,2,3,4,5,6,7,8,9};
        int target = 10;
        ArrayList<List<Integer>> addSum = addSum(arr, target);
        System.out.println(addSum);
        dfs(arr, target, 0, new ArrayList<>());
    }
    
    //dfs解法,k表示要凑的数,会越来越小;cur代表可选范围从cur到末尾
    public static void dfs(int[] arr, int k, int cur, ArrayList<Integer> res){
        //结束当前分支的条件
        //k=0表示找到了解
        if(k == 0){
            System.out.println(res);
            return;
        }
        if(k < 0 || cur == arr.length) return;
        //不要当前这位
        dfs(arr, k, cur+1, res);
        //要当前这位
        res.add(arr[cur]);
        int index = res.size()-1;
        dfs(arr, k-arr[cur], cur+1, res);
        //退回上一个状态
        res.remove(index);
    }
}

2. 剪枝

2.1 应用场景

  • DFS时,如果已经明确重当前状态无论如何都无法解决问题,就应该中断,不用再继续往下搜,这种方式称为剪枝
  • 数独、部分和里面都有剪枝

2.2 常见例题

2.2.1 n皇后问题

public class Q11_n皇后问题 {
    static int n;
    static int cnt;
    static int[] rec;

    public static void main(String[] args) {
        for (int i = 1; i < 11; i++) {
            n = i;
            rec = new int[n];
            dfs(0);
            System.out.println(n + "=" + cnt);
            cnt=0;
        }
    }

    public static void dfs(int row){
        if(row == n){
            cnt++;
            return;
        }
        //依次在某列尝试放一个皇后
        for (int col = 0; col < n; col++) {
            boolean ok = true;
            for (int i = 0; i < row; i++) {
                //这些情况不合适
                if(rec[i]==col || i+rec[i]==row+col || i-rec[i]==row-col){
                    ok = false;
                    break;
                }
            }
            if(ok){
                rec[row] = col; //标记
                dfs(row+1);
                rec[row] = 0;
            }
        }
    }
}

2.2.2 素数环

/*
输入一个n,有从1-n的数,将他们串成环,要求相邻两数相加都为素数
要求环的头为1
* */
public class Q12_素数环 {
    public static void main(String[] args) {
        for (int n = 1; n < 10; n++) {
            int[] rec = new int[n];
            rec[0] = 1;
            dfs(n, rec, 1);
            System.out.println("---------");
        }
    }

    private static void dfs(int n, int[] rec, int cur) {
        if(cur == n && isP(rec[0]+rec[n-1])){
            System.out.println(Arrays.toString(rec));
            return;
        }
        for (int i = 2; i <= n; i++) {
            if(check(rec, i, cur)){//rec中没有i这个数
                rec[cur] = i;
                dfs(n, rec, cur+1);
                rec[cur] = 0;
            }
        }
    }

    private static boolean check(int[] rec, int i, int cur) {
        for (int e : rec) {
            if(e==i || !isP(rec[cur-1]+i)){
                return false;
            }
        }
        return true;
    }

    private static boolean isP(int i) {
        for (int j = 2; j*j <= i; j++) {
            if(i % j == 0) return false;
        }
        return true;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值