经典问题:01背包、完全背包、多重背包、二维费用背包

本文详细介绍了01背包问题的解决方案,包括基本动态规划、优化的滚动数组方法以及完全背包和多重背包的变种。通过实例演示,展示了如何计算背包的最大价值,并探讨了背包问题在不同场景下的应用和优化技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

 

01背包

 

public class 背包01 {
    public static void main(String[] args) {
        int[] weight = {0, 1, 4, 3};//物品的重量
        int[] value = {0, 1500, 3000, 2000};//物品的单价
        int c = 4;//书包的容量
        dp(weight, value, c);
    }

    public static void dp(int[] weight, int[] value, int c) {
        int n = value.length - 1;//物品的个数
        //用于记录把物品放入书包的过程的表
        //totalValue[i][j]表示放入前i个物品,书包剩余容量为j时,能放入的物品的最大值
        int[][] totalValue = new int[n + 1][c + 1];//有n+1行,每行有c+1个元素
        /*
                    0磅     1磅     2磅     3磅     4磅
                    0       0       0       0       0
            吉他    0
            音响    0
            电脑    0
         */
        int[][] result = new int[n + 1][c + 1];//用于记录放入的过程
        for (int i = 1; i < n + 1; i++) {//从第一件物品开始放到最后一件物品
            for (int j = 1; j < c + 1; j++) {//放某一件物品时,从背包剩余重量为1到剩余重量为4
                if (weight[i] > j) {//如果放入的物品的重量大于背包剩余重量,则不能放入
                    totalValue[i][j] = totalValue[i - 1][j];//此时的总价值等于上一次放入物品时的总价值
                } else {
                    if (totalValue[i - 1][j] < value[i] + totalValue[i - 1][j - weight[i]]) {
                        //totalValue[i - 1][j]:放入上一个物品时的总价值
                        //value[i-1]:当前物品的价值
                        //totalValue[i-1][j - weight[i-1]]:放入当前物品后,剩余空间能够放入物品的最大值(在上一行找)
                        totalValue[i][j] = value[i] + totalValue[i - 1][j - weight[i]];
                        result[i][j] = 1;//此时是最大价值的存放情况
                    } else {
                        totalValue[i][j] = totalValue[i - 1][j];
                    }
                }
            }
        }
        print(totalValue);
        print(result);
        System.out.println("背包的最大价值是" + totalValue[n][c]);

        //totalValue[n][c]的物品放入情况就是最佳情况
        int i = result.length - 1;
        int j = result[0].length - 1;
        while (i > 0 && j > 0) {
            if (result[i][j] == 1) {
                System.out.print("把第" + i + "个物品放入背包" + "\n");
                j -= weight[i];//只有找到了,才需要改变背包的容量再继续找
            }
            i--;//不管找没找到,都需要退到前一行再继续找
        }

    }

    public static void print(int[][] arr) {
        for (int[] hang : arr) {
            for (int i : hang) {
                System.out.print(i + "    ");
            }
            System.out.println();
        }
    }

01背包优化

public class 背包01优化 {
    public static void main(String[] args) {
        int[] weight = {0, 1, 4, 3};//物品的重量
        int[] value = {0, 1500, 3000, 2000};//物品的单价
        int c = 4;//书包的容量
        youhua1(weight, value, c);
    }


    public static void youhua1(int[] weight, int[] value, int c) {
        int n = value.length - 1;//物品的个数
        int[] totalValue = new int[c + 1];//滚动数组,记录上一个物品放入的情况
        int[] temp = new int[c + 1];//记录当前个物品放入的情况
        for (int i = 1; i < n + 1; i++) {//从第一件物品开始放到最后一件物品
            for (int j = 1; j < c + 1; j++) {//放某一件物品时,从背包剩余重量为1到剩余重量为4
                if (weight[i] > j) {//如果放入的物品的重量大于背包剩余重量,则不能放入
                    temp[j] = totalValue[j];//此时的总价值等于上一次放入物品时的总价值
                } else {
                    temp[j] = Math.max(totalValue[j], value[i] + totalValue[j - weight[i]]);
                }
            }
            //每放完一次物品,就要把temp[]重新赋值给totalValue[]
            for (int x = 0; x < temp.length; x++) {
                totalValue[x] = temp[x];
            }
            System.out.println(Arrays.toString(totalValue));
        }

        System.out.println("背包的最大价值是" + totalValue[c]);
    }

    public static void youhua2(int[] weight, int[] value, int c) {
        int n = value.length - 1;//物品的个数
        int[] totalValue = new int[c + 1];//滚动数组,记录上一个物品放入的情况
        for (int i = 1; i < n + 1; i++) {//从第一件物品开始放到最后一件物品
            for (int j = c; j >= 1; j--) {//逆向遍历,防止数据被覆盖
                if (weight[i] <= j) {
                    totalValue[j] = Math.max(totalValue[j], value[i] + totalValue[j - weight[i]]);
                }
            }
            System.out.println(Arrays.toString(totalValue));
        }
        System.out.println("背包的最大价值是" + totalValue[c]);
    }
}

01背包变型(逆向)

public class 背包01变型 {
    public static void main(String[] args) {
        int[] weight = {0, 1, 4, 3};//物品的重量
        int[] value = {0, 1500, 3000, 2000};//物品的单价
        int n = 3;//一共有3种物品
        int c = 4;//至少装入的物品的总重量
        //需求改为,至少要装够c重量的物品,要求物品的总价值最小
        dp2(weight, value, n, c);
    }

    public static void dp(int[] weight, int[] value, int n, int c) {
        int[][] sum = new int[n + 1][c + 1];
        for (int x = 1; x < c + 1; x++) {//把第0行初始化为一个较大值
            sum[0][x] = 0x3f3f3f3f;
        }
        for (int i = 1; i < n + 1; i++) {//从第一个物品开始
            for (int j = 0; j < c + 1; j++) {//有可能至少装入的物品的重量为0
                if (weight[i] >= j) {//如果当前物品的重量大于当前需要的重量,要么放且仅放当前物品,要么不放
                    sum[i][j] = Math.min(sum[i - 1][j], value[i]);
                } else {//如果当前物品的重量小于当前需要的重量
                    sum[i][j] = Math.min(sum[i - 1][j], value[i] + sum[i - 1][j - weight[i]]);
                }
            }
        }
        System.out.println(sum[n][c]);
//        for (int x = 0; x < sum.length; x++) {
//            for (int y = 0; y < sum[x].length; y++) {
//                System.out.print(sum[x][y] + "\t");
//            }
//            System.out.println();
//        }
    /*
    w  v      0      1       2       3       4
    0  0      0      0x3f    0x3f    0x3f    0x3f
    1  1500   0      1500    0x3f    0x3f    0x3f
    4  3000   0      1500    3000    3000    3000
    3  2000   0      1500    2000    2000    3000
     */
    }

    public static void dp2(int[] weight, int[] value, int n, int c) {
        int[] sum = new int[c + 1];
        for (int x = 1; x < c + 1; x++) {//初始化为一个较大值
            sum[x] = 0x3f3f3f3f;
        }
        System.out.println(sum);
        for (int i = 1; i < n + 1; i++) {//从第一个物品开始
            for (int j = c; j >= 0; j--) {//有可能至少装入的物品的重量为0
                if (weight[i] >= j) {//如果当前物品的重量大于当前需要的重量,要么放且仅放当前物品,要么不放
                    sum[j] = Math.min(sum[j], value[i]);
                } else {//如果当前物品的重量小于当前需要的重量
                    sum[j] = Math.min(sum[j], value[i] + sum[j - weight[i]]);
                }
            }
            System.out.println(Arrays.toString(sum));
        }
        System.out.println(sum[c]);
    }
}

完全背包

每个物品的数量是无限的

public class 完全背包 {
    public static void main(String[] args) {
        int[] weight = {0, 1, 4, 3};//物品的重量
        int[] value = {0, 1500, 3000, 2000};//物品的单价
        int c = 4;//书包的容量
        dp2(weight, value, c);
    }

    public static void dp(int[] weight, int[] value, int c) {
        int n = value.length - 1;//物品的个数
        int[] totalValue = new int[c + 1];//滚动数组,记录上一个物品放入的情况
        for (int i = 1; i < n + 1; i++) {//从第一件物品开始放到最后一件物品
            for (int j = c; j >= 1; j--) {//逆向遍历,防止数据被覆盖
                for (int k = 0; k <= j / weight[i]; k++) {//从0个拿到当前容量下能拿的最大值
                    totalValue[j] = Math.max(totalValue[j], k * value[i] + totalValue[j - k * weight[i]]);
                }
            }
            System.out.println(Arrays.toString(totalValue));
        }
        System.out.println("背包的最大价值是" + totalValue[c]);
    }

    public static void dp2(int[] weight, int[] value, int c) {
        int n = value.length - 1;//物品的个数
        int[] totalValue = new int[c + 1];//滚动数组,记录上一个物品放入的情况
        for (int i = 1; i < n + 1; i++) {//从第一件物品开始放到最后一件物品
            for (int j = weight[i]; j < c + 1; j++) {//从前往后遍历,因为要用到本行的数据
                //从能放入物品的地方开始推
                totalValue[j] = Math.max(totalValue[j], value[i] + totalValue[j - weight[i]]);
            }
            System.out.println(Arrays.toString(totalValue));
        }
        System.out.println("背包的最大价值是" + totalValue[c]);
    }
}

多重背包

 朴素的方法


public class 多重背包 {
    public static void main(String[] args) {
        int[] w = {0, 1, 4, 3};//物品的重量
        int[] v = {0, 1500, 3000, 2000};//物品的单价
        int[] s = {0, 5, 1500, 800};//每种物品的个数
        int c = 10000;//背包容量
        dp(w, v, s, c);

    }

    public static void dp(int[] w, int[] v, int[] s, int c) {
        int n = w.length - 1;//有多少种物品
        int[] sum = new int[c + 1];
        for (int i = 1; i < n + 1; i++) {//从第一种物品开始装,到最后一种物品
            for (int j = c; j >= 1; j--) {//从背包的最大容量开始,到容量为1
                for (int k = 0; k <= s[i] && k * w[i] <= j; k++) {
                    sum[j] = Math.max(sum[j], k * v[i] + sum[j - k * w[i]]);
                }
            }
        }
        System.out.println(sum[c]);
    }

优化的方法(二进制)

自己写的垃圾代码

public class 多重背包 {
    public static void main(String[] args) {
        int[] w = {0, 1, 4, 3};//物品的重量
        int[] v = {0, 1500, 3000, 2000};//物品的单价
        int[] s = {0, 5, 1500, 800};//每种物品的个数
        int c = 10000;//背包容量
        dp2(w, v, s, c);

    }

    //二进制优化
    public static void dp2(int[] w, int[] v, int[] s, int c) {
        int n = w.length - 1;//有多少种物品
        int[] sum = new int[c + 1];
        int n2 = 0;//一共要拆分成多少个物品
        for (int x = 1; x < n + 1; x++) {
            n2 += depart(s[x])[0];
        }
        int[] w2 = new int[n2 + 1];//转化为01背包后,初始化重量的数组
        int[] v2 = new int[n2 + 1];//转化为01背包后,初始化单价的数组
        int m = 0;//拆分系数
        int count = 1;//记录新的数组的下标
        for (int x = 1; x < n + 1; x++) {//遍历原先的数组
            //计算第x个物品可以拆成多少个
            int nx = depart(s[x])[0];
            int y=0;
            for (y = 0; y < nx-1; y++) {
                //不管有没有余数,前面的都是一样的
                m = (int) Math.pow(2, y);
                w2[count] = m * w[x];
                v2[count] = m * v[x];
                count++;
            }
            //如果有余数,最后一个拆分系数不一样
            if(depart(s[x])[1]!=0){
                m=depart(s[x])[1];//有余数
            }else{
                m = (int) Math.pow(2, y);
            }
            w2[count] = m * w[x];
            v2[count] = m * v[x];
            count++;
        }
        for (int i = 1; i < n2 + 1; i++) {//从第一种物品开始装,到最后一种物品
            for (int j = c; j >= 1; j--) {//从背包的最大容量开始,到容量为1
                if (w2[i] <= j) {
                    sum[j] = Math.max(sum[j], v2[i] + sum[j - w2[i]]);
                }
            
        }
        System.out.println(sum[c]);
    }


    //计算一个整数n可以拆成多少个2的整数幂的和,余数是多少
    public static int[] depart(int n) {
        int[] arr = new int[2];
        int count = 0;
        int i = 0;
        while (i <= n) {
            i += Math.pow(2, count);
            count++;
        }
        if (i - Math.pow(2, count - 1) < n) {//有余数
            arr[0] = count;//多少个数
            arr[1] = n - i +(int) Math.pow(2, count - 1);//余数
        } else {
            arr[0]=count-1;//多少个数
            arr[1]=0;//余数
        }
        return arr;
    }
}

二维背包

 


public class 二维费用背包 {
    public static void main(String[] args) {
        int[] w = {0, 1, 4, 3};//物品的重量
        int[] s = {0, 2, 5, 3};//物品的体积
        int[] v = {0, 1500, 3000, 2000};//物品的单价
        int n = 3;//物品的个数
        int maxW = 4;//书包的最大重量容量
        int maxS = 5;//背包的最大体积容量
        int[][] sum = new int[maxW + 1][maxS + 1];
        for (int x = 1; x < n + 1; x++) {//从第一件物品开始放到最后一件物品
            for (int i = maxW; i >= 1; i--) {
                for (int j = maxS; j >= 1; j--) {
                    if (w[x] <= i && s[x] <= j) {
                        sum[i][j] = Math.max(sum[i][j], v[x] + sum[i - w[x]][j - s[x]]);
                    }
                }
            }
        }
        System.out.println("背包的最大价值是" + sum[maxW][maxS]);
    }
}

二维费用背包变型

 

public class 二维费用背包变型 {
    public static void main(String[] args) {
        int[] O = {0, 3, 10, 5, 1, 4};//每个气缸的氧气含量
        int[] N = {0, 36, 25, 50, 45, 20};//每个气缸的氮气含量
        int[] W = {0, 120, 129, 250, 130, 119};//每个气缸的重量
        int n = 5;//共有五种气缸
        int needO = 5;//需要的氧气
        int needN = 60;//需要的氮气
        dp2(O, N, W, n, needO, needN);
    }

    public static void dp(int[] O, int[] N, int[] W, int n, int needO, int needN) {
        int[][] sum = new int[needO + 1][needN + 1];//还需要i升氧气和j升氮气的情况下,气缸最小的总重量
        for (int x = 1; x < needO + 1; x++) {
            for (int y = 1; y < needN + 1; y++) {
                sum[x][y] = 0x3f3f3f3f;
            }
        }//初始化
        for (int x = 1; x < n + 1; x++) {//考虑带第1个气缸,第2个气缸,...,第n个气缸
            for (int i = needO; i >= 0; i--) {//有可能氧气的需求是0
                for (int j = needN; j >= 0; j--) {//有可能氮气的需求是0
                    if (O[x] >= i && N[x] >= j) {//如果当前的氧气和氮气的含量,比还需要的氧气和氮气大
                        sum[i][j] = Math.min(sum[i][j], W[x]);//如果不拿就是上一轮的气缸重量,如果拿就是当前气缸的重量
                    } else if (O[x] < i && N[x] < j) {//如果当前的氧气和氮气的含量,都比还需要的氧气和氮气小
                        sum[i][j] = Math.min(sum[i][j], W[x] + sum[i - O[x]][j - N[x]]);
                    } else if (O[x] >= i && N[x] < j) {//如果当前的氧气含量大于需求,氮气含量小于需求
                        sum[i][j] = Math.min(sum[i][j], W[x] + sum[i][j - N[x]]);//如果拿,氧气已经满足条件,只需要氮气满足条件
                    } else if (O[x] < i && N[x] >= j) {//如果当前的氮气含量大于需求,氧气含量小于需求
                        sum[i][j] = Math.min(sum[i][j], W[x] + sum[i - O[x]][j]);//如果拿,氮气已经满足条件,只需要氧气满足条件
                    }
                }
            }
        }
        int min = sum[needO][needN];
        System.out.println(min);
    }


    public static void dp2(int[] O, int[] N, int[] W, int n, int needO, int needN) {
        int[][] sum = new int[needO + 1][needN + 1];//还需要i升氧气和j升氮气的情况下,气缸最小的总重量
        for (int x = 1; x < needO + 1; x++) {
            for (int y = 1; y < needN + 1; y++) {
                sum[x][y] = 0x3f3f3f3f;
            }
        }//初始化
        for (int x = 1; x < n + 1; x++) {//考虑带第1个气缸,第2个气缸,...,第n个气缸
            for (int i = needO; i >= 0; i--) {//有可能氧气的需求是0
                for (int j = needN; j >= 0; j--) {//有可能氮气的需求是0
                    if (O[x] >= i && N[x] >= j) {//如果当前的氧气和氮气的含量,比还需要的氧气和氮气大
                        sum[i][j] = Math.min(sum[i][j], W[x]);//如果不拿就是上一轮的气缸重量,如果拿就是当前气缸的重量
                    } else {
                        int k = i - O[x] >= 0 ? i - O[x] : i;
                        int m = j - N[x] >= 0 ? j - N[x] : j;
                        sum[i][j] = Math.min(sum[i][j], W[x] + sum[k][m]);
                    }
                }
            }
        }
        int min = sum[needO][needN];
        System.out.println(min);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值