01背包问题
题目描述
有N种物品和一个容量为V的背包,每种物品只能用一次。
第i种物品的体积是v[i],价值是w[i]。
求解将哪些物品装入背包可使这些物品的体积总和不超过背包容量,且价值总和最大
例子
有一个背包,容量为4磅 , 现有如下物品
| 物品 | 重量 | 价值 |
|---|---|---|
| 吉他 | 1 | 1500 |
| 音响 | 4 | 3000 |
| 电脑 | 3 | 2000 |
要求达到的目标为装入的背包的总价值最大,并且重量不超出。
分解成一个个的小问题进行解决,假设存在背包容量大小有1,2,3,4,物品拥有也从【吉他】->【吉他、音响】->【吉他、音响、电脑】
| 背包容量 | 0磅 | 1磅 | 2磅 | 3磅 | 4磅 |
|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 | |
| 吉他(1) | 0 | 1500(吉他) | 1500(吉他) | 1500(吉他) | 1500(吉他) |
| 音响(4) | 0 | 1500(吉他) | 1500(吉他) | 1500(吉他) | 3000(音响) |
| 电脑(3) | 0 | 1500(吉他) | 1500(吉他) | 2000(电脑) | 3500(吉他+电脑) |
(1)当背包容量是0磅时或者没有物品时,能放入背包的价值就是0
| 背包容量 | 0磅 | 1磅 | 2磅 | 3磅 | 4磅 |
|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 | |
| 吉他(1) | 0 | ||||
| 音响(4) | 0 | ||||
| 电脑(3) | 0 |
(2)当物品只有吉他的时候,吉他重量1,那就是说背包容量大于等于1的地方都可以放假哦你去
| 背包容量 | 0磅 | 1磅 | 2磅 | 3磅 | 4磅 |
|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 | |
| 吉他(1) | 0 | 1500(吉他) | 1500(吉他) | 1500(吉他) | 1500(吉他) |
| 音响(4) | 0 | ||||
| 电脑(3) | 0 |
(3)当物品有吉他和音响的时候,音响重量4,背包容量1,2,3都放不下,这个时候就可以默认为音响这个物品无效,又只有吉他了,所以取上一层的策略
| 背包容量 | 0磅 | 1磅 | 2磅 | 3磅 | 4磅 |
|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 | |
| 吉他(1) | 0 | 1500(吉他) | 1500(吉他) | 1500(吉他) | 1500(吉他) |
| 音响(4) | 0 | 1500(吉他) | 1500(吉他) | 1500(吉他) | |
| 电脑(3) | 0 |
(4)当物品有吉他和音响的时候,音响重量4,背包容量为4时,音响可以放得下了,这个时候需要对比是放音响还是不放音响价值大,不放音响:取上层的策略,放音响:音响价值+剩余容量价值(取上层策略)
为什么要去上层策略呢?情况一,音响不放置,那就是当成音响这个物品失效了,所以走上层策略。情况二:因为音响已经放置了,所以剩余的容量肯定没有音响这个物品了,所以取上层策略
| 背包容量 | 0磅 | 1磅 | 2磅 | 3磅 | 4磅 |
|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 | |
| 吉他(1) | 0 | 1500(吉他) | 1500(吉他) | 1500(吉他) | 1500(吉他) |
| 音响(4) | 0 | 1500(吉他) | 1500(吉他) | 1500(吉他) | 3000(音响) |
| 电脑(3) | 0 |
(5)依次类推,求出第三层的价值
| 背包容量 | 0磅 | 1磅 | 2磅 | 3磅 | 4磅 |
|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 | |
| 吉他(1) | 0 | 1500(吉他) | 1500(吉他) | 1500(吉他) | 1500(吉他) |
| 音响(4) | 0 | 1500(吉他) | 1500(吉他) | 1500(吉他) | 3000(音响) |
| 电脑(3) | 0 | 1500(吉他) | 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(吉他+吉他),第一轮就都是在重复放置吉他
579

被折叠的 条评论
为什么被折叠?



