0-1 背包理论篇(上)——动态规划背包系列

一些废话

上面几篇记录了关于动态规划的基础题,这一篇就记录一下动态规划的经典背包问题之——0-1背包的基础知识,这里拆分为两篇来写,一篇为二维dp数组,第二篇为滚动数组(一维数组)。

0-1背包引入

所谓背包,是动态规划的一类类型题,并且有一套通用的解题思路和解题模板,0-1背包中的0-1代表  与 不放 两种情况,0-1背包需要多次学习至熟练于心,能够在脑海中明确每一步的含义,话不多说。

 物品的价值与重量如下表:

 背包最大只能装重量 4,现在将物品放入背包中,求如何放、放哪些物品才可以使得背包所背物品的价值之和最大。使用动态规划五部曲分析解解试试。

动态规划五部曲

本篇使用 二维数组 解决该0-1背包。

确定dp数组及下标含义

使用二维数组,则为 dp[ i ] [ j ] ,其中 i,j 的含义与遍历顺序有关,这里先假设 i 代表第 i + 1 个物品(因为物品数组下标是从 0 开始)、j 代表背包可以装下的 重量 ,那么 dp[ i ] [ j ] 代表 从下标 0 ~ i 中选取物品放入限重为 j 的背包中得到的背包中物品的价值之和的最大值

确定递推公式

确定递推公式必须要先捋清楚递推公式中的状态有哪些情况、从什么状态变化而来。推导0-1背包递推公式中,从遍历dp数组的过程来看,每次遍历dp数组(或者说每次确定每一个 dp[ i ] [ j ] )的过程中都有两个可以考虑的 状态 :放入当前物品到背包中、不放入当前物品到背包中。

  • 放入当前物品到背包中

        放入当前物品 i 到背包中,意味着要从 i - 1 中找到一个  dp[ i - 1 ] [ j - xxx ] 与当前物品 i 的价值 value[ i ] 相加得到当前的 dp[ i ] [ j ]。xxx 代表着当前物品 i 的重量 weight[ i ]。

  • 不放入当前物品到背包中

        不放入当前物品到背包中的情况则比较简单,直接沿用 dp[i - 1][ j ] 即可。

确定完了两种情况后,最终要取哪个?因为是要求得最大重量,故取最大值即可,当然,取最大值前还需要判断 j - weight[ i ]是否非负数,保证下标有意义,若不满足则直接沿用 dp[i - 1][ j ]即可。因此递推公式如下:

dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);

初始化dp数组

初始化需考虑递推公式,从递推公式可以知道,dp[ i ][ j ] 与 i - 1 j - weight[ i ] 相关(牢记 i 、j 、dp[ i ][ j ] 的含义),即求得 dp[ i ][ j ] 的前提是 dp[ i - 1 ][ 0 ]  ~ dp[ i - 1 ][ j ] 已知,因此 i = 0 的情况必须要先初始化,初始化为多少呢?

初始化 i = 0 是什么意思?i 代表物品,dp[ 0 ][ j ] 代表将物品 0 放入背包中,那么当背包可以装下物品 0 时,即将 dp[ 0 ][ j ] 初始化为物品 0 的价值 value[ 0 ]。并且 dp[ 0 ][ j ] 也只能初始化为 0 或者 value[ 0 ],因为当前 i = 0,只能从物品 0 ~ i 中选取物品放入背包。

另外,因为背包限重为 0 时,代表着无法放入任何物品,因此不论 i 为多少,dp[ i ][ 0 ] 都为 0,由于Java中int数组默认值为 0,故可以不初始化。

for (int i = 0; i <= bag; i++) {
    if (weight[0] <= i) dp[0][i] = value[0];
}

遍历顺序

最容易理解的是 外遍历物品内遍历背包,每遍历一次外层循环代表 从物品 0 ~ 物品 i 中选取物品放入背包。

for (int i = 1; i < value.length; i++) {//遍历物品
    for (int j = 1; j <= bag; j++) {//遍历背包
        //TODO 处理递推
    }
}

也可以 外遍历背包内遍历物品,这怎么理解? dp[ i ][ j ] 从 dp[ i - 1 ][ 0 ]  ~ dp[ i - 1 ][ j ] 中的某一个得来,所以,dp[ i ][ j ] 从左上面得来,如下图:

 以此而来,如果先遍历物品再遍历背包,那么就是在二维数组上一行一行从左往右遍历;如果先遍历背包再遍历物品,那么就是在二维数组是一列一列从上到下遍历,这两种遍历方向都是符合递推公式的。因此外遍历背包内遍历物品也是可行的。

for (int i = 1; i <= bag; i++) {//遍历物品
    for (int j = 1; j < value.length; j++) {//遍历背包
        //TODO 处理递推
    }
}

举例推导dp数组

 完整代码实现

public class Bag01Demo {
    public static void main(String[] args) {
        int[] value = {15, 20, 30};//物品价值
        int[] weight = {1, 3, 4};//物品重量
        int bag = 4;//背包限重
        int[][] dp = new int[value.length][bag + 1];
        //初始化
        for (int i = 0; i <= bag; i++) {
            if (weight[0] <= i) dp[0][i] = value[0];
        }
        //遍历
        for(int i = 1; i < value.length; i++) {//遍历物品
            for(int j = 1; j <= bag; j++) {//遍历背包
                if(weight[i] <= j) {
                    dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
                }else  dp[i][j] = dp[i - 1][j];
            }
        }
        //dp数组打印
        for(int i = 0; i < dp.length; i++) {
            for(int j = 0; j < dp[0].length; j++) {
                System.out.print(dp[i][j] + " ");
            }
            System.out.println("");
        }
    }
}

《代码随想录》刷题记

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值