Java数据结构:回溯算法之子集树

1、介绍一下子集树,什么是子集树?

当所给问题是从n个元素的集合S中找出满足某些条件或者性质的子集时,解空间是子集树;
比如典型的0-1背包问题-----and-----轮船装载问题:

解空间 就是指满足问题要求的所有解组成的集合,一个问题的解往往包含了得到这个解的每一步,就是抽象的对应解空间树的从根节点到叶子节点的一条路径。时间复杂度为O(2的n次方)。

下面现用我们老师给出的一个引入话题的例子,一个递归打印,理解一下打印出多少个hello world?

@Test
public void test01(){
    int[] arr = {1,2,3};
    backstrace01(arr, 0, arr.length);
}

private void backstrace01(int[] arr, int i, int length) {
    if(i == length){
        System.out.println("hello world!");
    } else {
        backstrace01(arr, i+1, length);
        backstrace01(arr, i+1, length);
    }
}

呐,这是结果,打印了8个hello world如图所示:

在这里插入图片描述
运行上面的代码可以看出来,打印出序列{1,2,3}的所有子集情况,也就是从根节点到叶子节点,每一条路径都是一个解,所以打印了8次hello world :

在这里插入图片描述
例如求一组整数序列,选择其中一部分整数,让选择的整数和与剩下的整数和的差最小:

public class ChildTree {
    static int[] arr = {12,52,60,13,32,28,38};
    static int[] x = new int[arr.length];
    static int[] bestx = new int[arr.length];
    static int r = 0;//没有选择的整数的和
    static int min = Integer.MAX_VALUE;

    public static void main(String[] args) {
        for (int i = 0; i < arr.length; i++) {
            r += arr[i];
        }
        backstrace(arr, 0);
        System.out.println("min:" + min);
        System.out.println(Arrays.toString(bestx));
    }

    private static void backstrace(int[] arr, int i) {
        if(i == arr.length){
            int sum = 0;
            for (int j = 0; j < arr.length; j++) {
                if(x[j] == 1){
                    sum += arr[j];
                }
            }

            // sum
            int ret = Math.abs(sum - r);
            if(ret < min){
                min = ret;
                for (int j = 0; j < x.length; j++) {
                    bestx[j] = x[j];
                }
            }
        } else {
            r -= arr[i];
            x[i] = 1;
            backstrace(arr, i+1);
            r += arr[i];
            x[i] = 0;
            backstrace(arr, i+1);
        }
    }
}

2、例1:0-1背包问题

问题描述:有一组物品,重量分别为:w1 w2 …wn, 价值分别:V1 V2 … Vn, 现要求背包承重为C, 问怎么装包才能使不超重的情况下价值达到最大?

(看到问题的第一瞬间想到的使运筹学的最优化问题,用动态规划来解决,在此用子集树解决)

因为这个问题的解是给出的原物品的一个子集,可用子集树来解决,然后添加剪枝操作,提高算法效率,这样也可以高效解决此问题:

public class ChildTreePackage {
    static int[] w = {5,8,7,9,6};
    static int[] v = {12,9,13,10,11};
    static int[] x = new int[w.length];
    static int[] bestx = new int[w.length];
    static int cw = 0;
    static int cv = 0;//已经选择物品的价值
    static int bestv = Integer.MIN_VALUE;
    static int c = 18;
    static int r = 0;

    public static void main(String[] args) {
        for (int i = 0; i < v.length; i++) {
            r += v[i];
        }
        backstrace(0);
        System.out.println("bestv:" + bestv);
        System.out.println(Arrays.toString(bestx));
    }

    private static void backstrace(int i){
        if(i == w.length){
            if(bestv < cv){
                bestv = cv;//更新最优值
                for(int j = 0;j < x.length;j++){//更新最优值选择的子集
                    bestx[j] = x[j];
                }
            }
        }else{
            r -= v[i];
            if(cw + w[i] <= c) {//左剪枝
                cw += w[i];
                cv += v[i];
                x[i] = 1;//向左走
                backstrace(i + 1);
                cw -= w[i];
                cv -= v[i];
            ![}](https://img-blog.csdnimg.cn/20190725230616402.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NDE4Nzk2Mw==,size_16,color_FFFFFF,t_70)
            //右剪枝
            if(cv + r > bestv) {//当前已选择物品的总价值+除右孩子以外的左孩子的值 > 当前最好的价值
                x[i] = 0;//向右走
                backstrace(i + 1);//i节点的右孩子
            }
            r += v[i];//不放在前面加回来是因为不需要加右孩子等不需要选取的值
        }
    }
}

结果中可以得到,最好的价值是36,然后下面的0表示不选,1表示加入背包:
在这里插入图片描述
3、例2:轮船装载问题

问题描述:有一组物品,其重量分别是:w1,w2…wn
现在有两艘轮船,其容量分别是C1和C2,满足w1+w2+…+wn <= C1 + C2
问怎么装载物品,才能够把物品全部装上轮船?

其实这个问题与0-1背包问题异曲同工,同样子集树解决:

public class 轮船装载问题 {
    static int[] w = {12,18,21,14,9,10};
    static int c1 = 55;
    static int c2 = 30;

    static int[] x = new int[w.length];
    static int[] bestx = new int[w.length];
    static int bestw = Integer.MIN_VALUE;
    static int cw = 0;
    static int r = 0; // 记录物品i后面所有物品的总重量

    public static void main(String[] args) {
        for (int i = 0; i < w.length; i++) {
            r += w[i];
        }
        backstrace(0);

        int sum = 0;
        for (int i = 0; i < bestx.length; i++) {
            if(bestx[i] == 0){
                sum += w[i];
            }
        }
        if(sum > c2){
            System.out.println("物品无法装载到轮船!");
            return;
        }

        System.out.println("C1:" + c1 + "装载物品:");
        for (int i = 0; i < bestx.length; i++) {
            if(bestx[i] == 1){
                System.out.print(w[i] + " ");
            }
        }
        System.out.println();

        System.out.println("C2:" + c2 + "装载物品:");
        for (int i = 0; i < bestx.length; i++) {
            if(bestx[i] == 0){
                System.out.print(w[i] + " ");
            }
        }
        System.out.println();
    }

    private static void backstrace(int i) {
        if(i == w.length){
            if(cw > bestw){
                bestw = cw;
                for (int j = 0; j < x.length; j++) {
                    bestx[j] = x[j];
                }
            }
        } else {
            r -= w[i];
            if(cw + w[i] <= c1){
                cw += w[i];
                x[i] = 1;
                backstrace(i+1);  // 选择第i个节点
                cw -= w[i];
            }

            if(cw + r > bestw){
                x[i] = 0;      // 才有必要去往右边的i节点
                backstrace(i+1); // 没选择第i个节点
            }

            r += w[i];
        }
    }
}

结果如下:
在这里插入图片描述

发布了45 篇原创文章 · 获赞 11 · 访问量 4825

猜你喜欢

转载自blog.csdn.net/weixin_44187963/article/details/97310600
今日推荐