背包问题之01背包

背包问题图解

在这里插入图片描述

01背包定义

N件物品和一个最多能背重量为W 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。

最基本的01背包问题

流程

dp数组定义
dp[i][j] 我们从下标0到i的物品任意选,背包容量为j,物品价值的最大值。
时刻记住这个定义。
递推公式定义
对于每个物品,我们只有两种状态,放与不放

  • 不放物品:那么dp[i][j] = dp[i-1][j],和之前没什么两样
  • 放物品,这时就要考虑放进去之后的价值和不放的比较了
    dp[i-1][j-weight[i]] 是从0到i-1任选物品,放进j-weight[i]容量的背包的价值大小,还要再加上value[i],这是放物品的价值

此时,递推公式为:dp[i][j] = Math.max(dp[i - 1][j],dp[i - 1][j - weight[i]] + value[i]);
dp数组初始化
如果j为0,意思是背包容量为0,那么什么都放不进去,所以价值肯定是0,所以dp[i][0] 初始化为0。
同时dp[i][j]又是由i-1推导得来,所以要初始化i=0时的dp,即为把下标为0的物品放进容量从0到j的背包里,这时会有两种情况,能放下0号物品,放不下0号物品,放不下初始化为0,放的下就初始化为value[0]。
其余的dp[i][j] 都是由左上方推导得来,所以初始化什么都可以。
遍历顺序
两个维度,先遍历物品,和先遍历背包都可以,因为dp的定义都是由左上角得来所以无所谓。
最后自己推推看就行了。

其实我们可以发现,在物品遍历时,我们可以把dp[i-1]那层拷贝到dp[i]上,那么表达式可以为

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

这是什么意思呢,就是每个背包容量对应的价值都要和之前的自己比比,这样就压缩了一层空间,但是与其这样,我们直接用一维数组不好吗?于是我们有了一维dp数组,也叫滚动数组

一维dp(滚动数组)

同样的步骤,
dp数组含义
dp[j]表示为:容量为j的背包,物品价值最大可以是dp[j]
dp递推公式

dp[j]由dp[j-weight[i]]推导而来,放入物品i时,我们有两种选择,第一种是选自己,相当于二维dp中的dp[i-1][j],即不放物品i,另一个为选dp[j-weight[i]] + value[i],即取最大的。
所以递推公式为:

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

初始化
dp[0] = 0没什么好说的,其他值呢?因为在推导dp数组时,要取最大的数,若题目给的价值都为正整数,那么非零下标初始化为0即可。这样就可保证递推过程中dp数组不被初始值覆盖了。
遍历顺序

for(int i = 0; i < weight.size(); i++) { // 遍历物品
    for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量
        dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
    }
}

这里需要注意,背包容量的遍历顺序是和二维不一样的,二维遍历时,背包容量是从小到大,一维背包是从大到小。
why?
倒序遍历是为了物品i只被放入一次
栗子:
下标 0 1 2
容量1 5 8
价值6 12 30
正序遍历时,
dp[1] = dp[1-weight[0]] + value[0] = 6
dp[2] = dp[2-weight[0]] + value[0] = 12
发现了没有,下标为0的物品放入了两次,这是为什么呢?这就跟它的遍历顺序有关,因为递推是由上一个状态推导来的,所以从前往后势必会导致重复放入物品,所以要倒序放入物品
dp[2] = dp[2-weight[0]] + value[0] = 6
dp[1] = dp[1-weight[0]] + value[0] = 6
那又有人要问了,二维怎么不需要倒序呢,因为二维的递推不是由上一个,是由上一层,本层的dp[i][j]不会被覆盖。
那又有人要问了,嵌套的顺序可以改不,也不可以。你想想,现在是内层先遍历容量才可以把所有的物品遍历完,此时要是先遍历容量,内层遍历物品会发生什么,此时dp的j不变,遍历了一圈,比了一个,扔了一个,做无用功,每个dp[j]只放了一个物品,这里不懂纸上两种模拟一下你就懂了。

一维与二维区别

一维要简洁的多,而且空间复杂度降了一个数量级

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值