三种背包问题(01背包、完全背包、多重背包)【动态规划】 01背包 完全背包 多重背包


转载自 【动态规划】三种背包问题(01背包、完全背包、多重背包)

问题描述

给定n个物体(它们的重量为:w1,w2,......,wn,价值为:v1,v2,......,vn) 和 一个承受重量为W的背包,
问怎么选取这些物体,放在背包中(不超过背包的承重),让所取的子集达到最大价值。

实现思路

穷举法

只要给出n个物体的所有组合(子集),分别各个子集的总价值,去掉那些总重量超过背包承重W的子集之后,
对剩下的子集中找到总价值最大的那一个,就是我们想要的结果了。
由于n各物体的有2n个子集,所以上面的做法的时间复杂度将是O(2n),除非n很小,否则时间性能消耗非常严重。

01背包算法基本实现

/**
 * @Author hwj
 * @Date 2020/8/16 22:09
 * @Desc: 给定n个物体(它们的重量为:w1,w2,......,wn,价值为:v1,v2,......,vn) 和 一个承受重量为W的背包,
 * 问怎么选取这些物体,放在背包中(不超过背包的承重),让所取的子集达到最大价值。
 **/
public class package01 {
    public static void main(String[] args){
        //w[]:物品重量,v[]:物品价值,m:背包承重,n:物品个数,maxValue[][]:状态
        int[] w = {0, 10, 3, 4, 5}; //第一个数为0,是为了让输出时,i表示有i个物品
        int[] v = {0, 3, 4, 6, 7};
        int m = 10;
        int n = 4;
        int[][] maxValue = new int[5][16];
        // 01背包算法
        for (int i=1; i<=n; i++) { //第一个物体 是 第1行
            for (int j=0; j<=m; j++) {
                // 在背包承重为 j 时,所能达到的最大价值
                if (i > 0) {
                    // maxValue[i-1][j]:最大价值为 在承重为 j 的情况下,不放入第i物体的最大价值
                    maxValue[i][j] = maxValue[i-1][j];
                    // 当承重开始超过第i个,比较 不放入i物体 与 放入 i 物体相比
                    if (j >= w[i]) {
                        maxValue[i][j] = max(maxValue[i][j], maxValue[i-1][j-w[i]] + v[i]);
                        //注:maxValue[i][j]其实就是maxValue[i-1][j]   因为上面的赋值
                    }
                } else {          //初始化,只考虑一个物体
                    if (j >= w[1]) {
                        maxValue[1][j] = v[1];
                    }
                }
            }
        }

        System.out.println("4个物品在背包承重为10的情况下的组合的最大价值为:"+maxValue[n][m]);
        System.out.println();

        // 打印背包的不同承重量
        System.out.print("   " + "	");
        for (int i=0; i<=m; i++) {
            System.out.print(i + "	");
        }
        System.out.println();

        // 打印01背包算法 得到的状态矩阵值
        for (int i=1; i<=n; i++) {
            System.out.print("i="+ i +"	");
            for (int j=0; j<=m; j++) {
                System.out.print(maxValue[i][j]+"	");
            }
            System.out.println();
        }
    }

    public static int max(int a, int b) {
        if (a > b) {
            return a;
        }
        return b;
    }
}

一维数组实现 01背包

01背包还可以用一维数组实现,只不过此时的递推式 & 初始条件就需要做些改变了。
要想用一维数组存放所有状态,也就是让该数组某个时间是第 i-1 层的状态,而过一段时间之后则成为第 i 层的状态。
覆盖的过程中,应该采用从后到前的顺序遍历。首先,改写maxValue[ W ]的值。
这是由于计算 i层 的maxValue[ W-1 ] 不需要用到 i-1 的maxValue[ W ]状态,所以,maxValue[ W ]的改动不影响maxValue[ W-1 ]的计算。
以此类推,就可以在原来的数组上面不断覆盖最新一层的状态值了。
/**
 * @Author hwj
 * @Date 2020/8/16 22:09
 * @Desc: 给定n个物体(它们的重量为:w1,w2,......,wn,价值为:v1,v2,......,vn) 和 一个承受重量为W的背包,
 * 问怎么选取这些物体,放在背包中(不超过背包的承重),让所取的子集达到最大价值。
 **/
public class package01 {
    public static void main(String[] args){
        //w[]:物品重量,v[]:物品价值,m:背包承重,n:物品个数,maxValue[][]:状态
        int[] w = {0, 10, 3, 4, 5}; //第一个数为0,是为了让输出时,i表示有i个物品
        int[] v = {0, 3, 4, 6, 7};
        int m = 10;
        int n = 4;
        int[] maxValue = new int[16];
        // 01背包算法
        for (int i=1; i<=n; i++) {
            for (int j=m; j>=w[i]; j--) {
                maxValue[j] = max(maxValue[j], maxValue[j-w[i]] + v[i]);
            }

            //验证  结果和二维实现的输出结果完全一样
            //for (int k=0; k<=m; k++) {
            //	System.out.print(maxValue[k] + "	");
            //}
            //System.out.println();
        }


        System.out.println("4个物品在背包承重为10的情况下的组合的最大价值为:"+maxValue[m]);
        System.out.println();

        // 打印背包的不同承重量
        System.out.print("   " + "	");
        for (int i=0; i<=m; i++) {
            System.out.print(i + "	");
        }
        System.out.println();

        // 打印01背包算法 得到的状态矩阵值
        for (int i=1; i<=n; i++) {
            System.out.print("i="+ i +"	");
            for (int j=0; j<=m; j++) {
                System.out.print(maxValue[j]+"	");
            }
            System.out.println();
        }
    }

    public static int max(int a, int b) {
        if (a > b) {
            return a;
        }
        return b;
    }
}

完全背包

问题描述

完全背包是在01背包的基础上加了个条件——这n种物品都有无限的数量可以取,问怎样拿才可以实现价值最大化。

问题分析

虽然题意中每种有无限件,但这里有个隐藏条件:背包承重量的固定性导致每种最多只能取某个值,再多就放不下了,这个值就是W / wi。也就是说,
对于第 i 种物品,它可以取0,1,2,......,W / wi(向下取整)件。而在01背包中,对于第 i 种物品,只能取0,1件。我们可以看到,
01背包其实就是完全背包的一个特例。所以我们可以用类似01背包的思路写出完全背包的基本算法。

具体实现代码

二维数组实现

/**
 * @Author hwj
 * @Date 2020/8/16 23:30
 * @Desc:
 **/
public class PackageComplete {
    public static void main(String[] args) {
        int[] w = {0,10, 3, 4, 5};
        int[] v = {0,3, 4, 6, 7};
        int m = 10;
        int n = 4;
        int[][] maxValue = new int[5][16];

        for (int i=1; i<=n; i++) {
            for (int j=0; j<=m; j++) {
                if (i > 1) {
                    maxValue[i][j] = maxValue[i-1][j];
                    //if (j >= v[i]) {
                    //	maxValue[i][j] = max(maxValue[i][j], maxValue[i-1][j-v[i]] + w[i]);
                    //}
                    if (j/w[i] >= 1) {
                        int maxTmp = 0;
                        // 对于i个物品,进行j/w[i]次比较得到最大值;而01背包中只需要进行1次比较
                        for (int k=1; k<=j/w[i]; k++) {
                            if (maxValue[i-1][j-k*w[i]] + k*v[i] > maxTmp) {
                                maxTmp = maxValue[i-1][j-k*w[i]] + k*v[i];
                            }
                        }
                        maxValue[i][j] = max(maxValue[i][j], maxTmp);
                    }
                } else {
                    //if (j >= v[0]) {
                    //	maxValue[0][j] = w[0];
                    //}
                    if (j/w[1] >= 1) {
                        maxValue[1][j] = j/w[1] * v[1];
                    }
                }
            }
        }

        System.out.println("4个物品在背包承重为10的情况下的组合的最大价值为:"+maxValue[n][m]);
        System.out.println();

        // 打印背包的不同承重量
        System.out.print("   " + "	");
        for (int i=0; i<=m; i++) {
            System.out.print(i + "	");
        }
        System.out.println();

        // 打印01背包算法 得到的状态矩阵值
        for (int i=1; i<=n; i++) {
            System.out.print("i="+ i +"	");
            for (int j=0; j<=m; j++) {
                System.out.print(maxValue[i][j]+"	");
            }
            System.out.println();
        }
    }

    public static int max(int a, int b) {
        if (a > b) {
            return a;
        }
        return b;
    }

}

一维数组实现

/**
 * @Author hwj
 * @Date 2020/8/16 23:30
 * @Desc:
 **/
public class PackageComplete {

    public static void main(String[] args) {
        int[] w = {0,10, 3, 4, 5};
        int[] v = {0,3, 4, 6, 7};
        int m = 10;
        int n = 4;
        int[] maxValue = new int[16];

        for (int i=1; i<=n; i++) {
            //for (int j=m; j>=w[i]; j--) {
            // 正序遍历; 01背包是逆序遍历
            for (int j=w[i]; j<=m; j++) {
                maxValue[j] = max(maxValue[j], maxValue[j-w[i]] + v[i]);
            }

            //验证  结果和二维实现的输出结果完全一样
            //for (int k=0; k<=m; k++) {
            //	System.out.print(maxValue[k] + "	");
            //}
            //System.out.println();
        }

        System.out.println("4个物品在背包承重为10的情况下的组合的最大价值为:"+maxValue[m]);
        System.out.println();

        for (int i=0; i<=m; i++) {
            System.out.print(maxValue[i] + "	");
        }
    }

    public static int max(int a, int b) {
        if (a > b) {
            return a;
        }
        return b;
    }
}

多重背包

问题描述

多重背包是在01背包的基础上,加了个条件:第 i 件物品有ni件。

问题分析

我们考虑一下,如果所有ni都满足ni ≥ W / wi,那不就变成完全背包的问题了么。可见,完全背包的基本实现思路也可以应用到多重背包的基本实现。对于多重背包的基本实现,与完全背包是基本一样的,不同就在于物品的个数上界不再是v/c[i]而是n[i]与v/c[i]中较小的那个。所以我们要在完全背包的基本实现之上,再考虑这个上界问题。
三种背包问题(01背包、完全背包、多重背包)【动态规划】
01背包
完全背包
多重背包

代码实现如下所示,代码与完全背包的区别除了多了个表示物品个数的数组n[ ]之外,只在内循环的控制条件那里。

## 二维数组实现
/**
 * @Author hwj
 * @Date 2020/8/16 23:40
 * @Desc:
 **/
public class MultiplePackage {
    public static void main(String[] args) {
        int[] w = {0,10, 3, 4, 5};
        int[] v = {0,3, 4, 6, 7};
        //第i个物品对应的个数
        int[] mount = {0,5,1,2,1};
        int m = 10;
        int n = 4;
        int[][] maxValue = new int[5][16];

        for (int i=1; i<=n; i++) {
            for (int j=0; j<=m; j++) {
                if (i > 1) {
                    maxValue[i][j] = maxValue[i-1][j];
                    if (j/w[i] >= 1) {
                        int maxTmp = 0;
                        //for (int k=1; k<=j/w[i]; k++) {
                        //多重背包与完全背包的区别只在内循环这里
                        for (int k=1; k<=j/w[i] && k<=mount[i]; k++) {
                            if (maxValue[i-1][j-k*w[i]] + k*v[i] > maxTmp) {
                                maxTmp = maxValue[i-1][j-k*w[i]] + k*v[i];
                            }
                        }
                        maxValue[i][j] = max(maxValue[i][j], maxTmp);
                    }
                } else {
                    if (j/w[1] >= 1) {
                        maxValue[1][j] = j/w[1] * v[1];
                    }
                }
            }
        }

        System.out.println("4个物品在背包承重为10的情况下的组合的最大价值为:"+maxValue[n][m]);
        System.out.println();

        // 打印背包的不同承重量
        System.out.print("   " + "	");
        for (int i=0; i<=m; i++) {
            System.out.print(i + "	");
        }
        System.out.println();

        // 打印01背包算法 得到的状态矩阵值
        for (int i=1; i<=n; i++) {
            System.out.print("i="+ i +"	");
            for (int j=0; j<=m; j++) {
                System.out.print(maxValue[i][j]+"	");
            }
            System.out.println();
        }
    }

    public static int max(int a, int b) {
        if (a > b) {
            return a;
        }
        return b;
    }

}