动态规划之01背包问题

01背包问题

题目描述

有N种物品和一个容量为V的背包,每种物品只能用一次。
第i种物品的体积是v[i],价值是w[i]。
求解将哪些物品装入背包可使这些物品的体积总和不超过背包容量,且价值总和最大

例子

有一个背包,容量为4磅 , 现有如下物品

物品重量价值
吉他11500
音响43000
电脑32000

要求达到的目标为装入的背包的总价值最大,并且重量不超出。

分解成一个个的小问题进行解决,假设存在背包容量大小有1,2,3,4,物品拥有也从【吉他】->【吉他、音响】->【吉他、音响、电脑】

背包容量0磅1磅2磅3磅4磅
00000
吉他(1)01500(吉他)1500(吉他)1500(吉他)1500(吉他)
音响(4)01500(吉他)1500(吉他)1500(吉他)3000(音响)
电脑(3)01500(吉他)1500(吉他)2000(电脑)3500(吉他+电脑)

(1)当背包容量是0磅时或者没有物品时,能放入背包的价值就是0

背包容量0磅1磅2磅3磅4磅
00000
吉他(1)0
音响(4)0
电脑(3)0

(2)当物品只有吉他的时候,吉他重量1,那就是说背包容量大于等于1的地方都可以放假哦你去

背包容量0磅1磅2磅3磅4磅
00000
吉他(1)01500(吉他)1500(吉他)1500(吉他)1500(吉他)
音响(4)0
电脑(3)0

(3)当物品有吉他和音响的时候,音响重量4,背包容量1,2,3都放不下,这个时候就可以默认为音响这个物品无效,又只有吉他了,所以取上一层的策略

背包容量0磅1磅2磅3磅4磅
00000
吉他(1)01500(吉他)1500(吉他)1500(吉他)1500(吉他)
音响(4)01500(吉他)1500(吉他)1500(吉他)
电脑(3)0

(4)当物品有吉他和音响的时候,音响重量4,背包容量为4时,音响可以放得下了,这个时候需要对比是放音响还是不放音响价值大,不放音响:取上层的策略,放音响:音响价值+剩余容量价值(取上层策略)
为什么要去上层策略呢?情况一,音响不放置,那就是当成音响这个物品失效了,所以走上层策略。情况二:因为音响已经放置了,所以剩余的容量肯定没有音响这个物品了,所以取上层策略

背包容量0磅1磅2磅3磅4磅
00000
吉他(1)01500(吉他)1500(吉他)1500(吉他)1500(吉他)
音响(4)01500(吉他)1500(吉他)1500(吉他)3000(音响)
电脑(3)0

(5)依次类推,求出第三层的价值

背包容量0磅1磅2磅3磅4磅
00000
吉他(1)01500(吉他)1500(吉他)1500(吉他)1500(吉他)
音响(4)01500(吉他)1500(吉他)1500(吉他)3000(音响)
电脑(3)01500(吉他)1500(吉他)2000(电脑)3500(吉他+电脑)

(6)最后的最优结果就是背包容量为4,物品有三个的时候的价值:3500(吉他+电脑)

二维数组代码实现

let weight = [0,1,4,3]  //物品重量
let value = [0,1500,3000,2000] // 物品价值
let bagWeight = 4 //背包容量
 

// 二维数组代码(可使用空间优化 优化为一维数组)
function bagProblem(weight,value,bagWeight) {
    const dp = new Array(weight.length).fill(0).map(()=>new Array(bagWeight+1).fill(0))
    for(let i = 1 ; i < weight.length ; i++) { //第几个物品
       for(let j = 1 ; j <= bagWeight ; j++) { //背包容量 
            if(j < weight[i]) { //要放入背包的物品超出当前容量 无法放下
                dp[i][j] = dp[i-1][j]
            }else { // 可以放下
                dp[i][j] = Math.max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i])
            }
        }
    }
    console.log(dp)
    return dp[weight.length-1][bagWeight]
}
 
console.log(bagProblem(weight,value,bagWeight))  //3500

一维数组代码实习

我们可以发现在计算dp[i][j]最优解的过程中我们只用到了i和i-1,利用滚动数组特征,我们可以将二维数组转化为一维数组

function youhuabagProblem(weight,value,bagWeight) {
    const dp = new Array(bagWeight+1).fill(0)

    for(let i = 0 ; i < weight.length ; i++) { //物品数量
       for(let j = bagWeight ; j >=weight[i]; j--) { // 背包容量 
            dp[j] = Math.max(dp[j],dp[j-weight[i]]+value[i]);
        }

      //正序代码
       //  for(let j = weight[i] ; j <=bagWeight; j++) { // 背包容量 
	    //       dp[j] = Math.max(dp[j],dp[j-weight[i]]+value[i]);
	    //   }
	     console.log(`${i}`,dp)
    }
    console.log(dp)
    return dp[bagWeight]
}
 
console.log(youhuabagProblem(weight,value,bagWeight))  //3500

注意点:第二层循环一定要逆序,否则结果将不正确

为什么一维数组的实现要逆序?

逆序dp数组结果[ 0, 1500, 1500, 2000, 3500 ]
非逆序dp数组结果 [ 0, 1500, 3000, 4500, 6000 ]

非逆序的情况

看下每一轮的输出:
第0轮 [ 0, 0, 0, 0, 0 ]
第1轮 [ 0, 1500, 1500, 1500, 1500 ]
第2轮 [ 0, 1500, 1500, 1500, 3000 ]
第3轮 [ 0, 1500, 1500, 2000, 3500 ]

可以看到输出的就是二维数组的每一行
拿第一轮(只有吉他这一个物品)举例,当更新dp[4]时,不放置吉他,结果为0,放置吉他,结果为1500,再去更新dp[3],也是如此,dp[2],dp[1]同理
因为我们逆序一直是从后往前更新的,所以我们能保证前面的数据还是上次求值的数据(也就是上一层的结果)

逆序的情况

看下每一轮的输出:
第0轮 [ 0, 0, 0, 0, 0 ]
第1轮 [ 0, 1500, 3000, 4500, 6000 ]
第2轮 [ 0, 1500, 3000, 4500, 6000 ]
第3轮 [ 0, 1500, 3000, 4500, 6000 ]

看下第一轮的输出,我们可以看到结果不对,为什么呢?
因为我们是正序更新
我们首先更新dp[1]为1500,当更新dp[2]原本应该拿上一层的结果[ 0, 0, 0, 0, 0 ],也就是dp[0]=0去更新的,但是dp[1]已经t提前被更新过了,已经不是上一层的结果了,被更新为当前层的结果了,所以导致结果不对,我们就重复防止了吉他这个物品,所以结果dp[2]是3000(吉他+吉他),第一轮就都是在重复放置吉他

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值