01背包问题学习(动态规划)

01背包问题:

给定n个物品和一个最多能背重量为w 的背包。数组weights[i]含义为第i个物品的重量,values[i] 含义为第i个物品的价值。每件物品只能用一次,求哪些物品装入背包里可以使得物品价值总和最大?

使用动态规划来解决该问题时首先要搞明白动态规划数组dps的每个值的含义以及递推方程。

解法一:使用二维数组

1、dps数组的定义

dps数组使用二维数组,第一维含义为依次选择是否放入 i 号物体,第二维含义为背包的最大重量。

那么dps[i][j] 就表示当选择是否放入 i 号物品后,背包最大重量为 j 时的最大价值。

举个例子:

dps[0][j]表示当选择是否放入0号物品后,背包最大重量为 j 时的最大价值。

显然,当 j >= weights[0]时候,我们肯定要放入0号物品,因为这时候可以取得最大价值,最大价值就是的0号物品的价值。当 j < weights[0] 时,我们无法放入0 号物品,这样最大价值就是0。

上述例子中,为什么只考虑0 号物品?因为dps数组的定义就是依次考虑给出的物体,i 为0时只用考虑 0 号物体,为1 时则0 号物品已经考虑完了,正在考虑是否要放入1 号物体。

2、dps数组的递推公式

当考虑是否放入i 号物体时,有两种情况,

(1)不放入i 号物体

当不放入i 号物体时,dps[i][j]显然等于考虑完i - 1号物体后的最大价值,即

dps[i - 1][j]; 

(2)放入 i 号物体

当放入i 号物体时,此时的背包的价值(注意,此时说的是放入i 号物体后的价值,而不是dps[i][j], dps[i][j] 为最大价值)为放 i 物体之前,且背包重量为减去 i 物体重量的最大价值 + 当前i物体的价值,即

dps[i - 1][j - weight[i]] + values[i]

对于dps [i][j] ,上述两种情况取最大值即可,即

dps[i][j] = Math.max(dps[i - 1][j], dps[i - 1][j - weights[i]] + values[i]);

当然,如果 j < weights[i],即当前背包重量小于i号物体重量,就没得选了,只能选中不放入 i 号物体。

3、dps数组的初始化

由上述递推公式,发现dps数组只取决于前一状态时刻,故只需要将第一行初始化即可,

在dps数组定义里举的例子即为dps数组的初始化。

最终代码如下:

    public int maxValue(int[] values, int[] weights, int bagWeight) {

        // dps[i][j]表示当选择是否放入i号物品后,背包最大重量为j时的最大价值
        // dps[0][j]表示当查看是否放入0号物品后,背包最大重量为j时的最大价值
        int[][] dps = new int[weights.length][bagWeight + 1];
        // 初始化,dps[i][0] 显然为0,因为背包最大重量为0,放不进任何物品
        // dps[0][j] 取决于0号物品的重量,当j>weights[0]时,初始化为该物品价值,否则为0
        for (int j = 1; j <= bagWeight; j++) {
            if (j >= weights[0]) dps[0][j] = values[0];
        }

        // 遍历物体
        for (int i = 1; i < weights.length; i++) {
            for (int j = 1; j <= bagWeight; j++) {
                // 两种情况,放入i物品和不放i物品
                // 放i物品的价值为放i物体之前,且背包重量为减去i物体重量的最大价值 + 当前i物体的价值
                // dps[i][j] = dps[i - 1][j - weight[i]] + values[i]
                // 不放i物体,最大价值为考虑完是否放入上一个物体的最大价值
                if (j < weights[i]) {
                    dps[i][j] = dps[i - 1][j]; // 当j小于i物体重量时,肯定不能放i物体
                } else {
                    dps[i][j] = Math.max(dps[i - 1][j], dps[i - 1][j - weights[i]] + values[i]);
                }
            }
        }
        System.out.println(Arrays.deepToString(dps));
        return dps[weights.length - 1][bagWeight];
    }

解法二:使用一维数组

1、dps数组的定义

dps数组使用一维数组,dps[i]就表示背包最大重量为 i 时的最大价值。

那怎么进行数组更新?其实本质上同二维数组相同,还是依次考虑是否放入各个物体,然后更新dps数组,直到所有物体都考虑完毕,只不过一维数组变成了自我更新。所以依然需要双重循环。先遍历物体,每考虑一个物体,更新一遍dps数组(即考虑完 i 号物体后的最大价值)。

2、dps数组的递推公式

同样,当考虑是否放入i 号物体时,有两种情况,

(1)不放入i 号物体

当不放入i 号物体时,dps[i]显然等于考虑完i - 1号物体后的最大价值,即其本身不变,此处的 j 依然是背包重量

dps[j] = dps[j]; 

(2)放入 i 号物体

当放入i 号物体时,此时背包的价值为放 i 物体之前,且背包重量为减去 i 物体重量的最大价值 + 当前i物体的价值,即

dps[j] - weight[i]] + values[i]

对于dps[j],上述两种情况取最大值即可,即

dps[j] = Math.max(dps[j], dps[j - weights[i]] + values[i]);

同样,如果 j < weights[i],即当前背包重量小于i号物体重量,就没得选了,只能选中不放入 i 号物体。

注意:由于dps数组依赖于上一步dps数组(即考虑完 i - 1物体后的dps数组),且是重量大的背包依赖于上一步重量小的背包(映射到dps数组,就是数组右侧依赖于上一步数组左侧,从j - weights[i]可以看出),所以在dps数组自我更新时,要从右往左更新。

3、dps数组的初始化

初始化同二维数组类似

最终代码如下:

    public int maxValue1(int[] values, int[] weights, int bagWeight) {
        // 一维版本
        int[] dps = new int[bagWeight + 1]; // dps[i]表示背包容量为i时最大能有多少价值
        // 初始化
        for (int i = 0; i <= bagWeight; i++) {
            if (i >= weights[0]) dps[i] = values[0];
        }
        for (int i = 0; i < weights.length; i++) {
            for (int j = bagWeight; j > 0; j--) { // 舍弃0号位,重量为0时价值始终为0
                // 两种情况,是否选择放入i物品
                // 放入, 最大价值就是 dps[j - weights[i]] + values[i]
                // 不放入,最大价值为dps[j],相当于i - 1 物品时的重量
                if (j >= weights[i]) dps[j] = Math.max(dps[j], dps[j - weights[i]] + values[i]);
            }
            System.out.println(Arrays.toString(dps));
        }
        return dps[bagWeight];
    }

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

liuskuif

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值