01背包问题是什么?
假设现在有物品[a,b,c,d,e] 5件,
每件物品的重量(KG)不同,分别为[3,4,1,8,4]
每件物品的价值不同,分别为[1,2,3,4,5],
现在小明现在用可以装12KG的背包一个,
想带走最多价值的物品,
请问最多可以带走的价值是多少?
分别带走哪些物品?
注意:每个物品最多只能带一个
思路:贪心算法 不行
最容易想到的思路:
- 重量大于背包的不考虑,因为放不下
- 重量大价值小的不考虑,因为价值不高
是不是就挑选 价值/重量 大的就可以了呢?(也即贪心算法,每次选择当前最优)
看个例子:
假设有 5 个物品,N = 5,背包容量为 100
每种物品的质量与价值如下:
- value/weight = 20/20 = 1
- value/weight = 30/30 = 1
- value/weight = 44/40 = 1.1
- value/weight = 55/50 = 11.1
- value/weight = 60/60 = 1
如果按上述策略:优先选“价质比”最大的是: (第三个+第四个)
此时质量:40+50=90 价值:44+55 =99
实际更优的选择策略是: (第一个 + 第二个 + 第四个)
此时质量:20+30+50=100 价值:20+30+55=105
虽然1,2高价质比不是最高,但是1,2,4能装满整个背包,也就是最佳组合
=》所以,“价质比”这种贪心策略显然不是最优策略。
原因是:这个问题包含2个层面最优,1是最高价值/重量比,2是最佳组合
因为这个算法仅仅满足高价质比,没有满足最佳组合
思路:动态规划 可以
dp[i][j] 为将前i件物品装进容量为j的背包可以获得的最大价值
dp表为(N+1)x(C+1)维的二维数组 0<=i<=N, 0<=j<=C
状态转移方程
- 如果w[i] > j,则背包放不下,dp[i][j] = dp[i-1][j];
- 如果w[i] <= j,则第i件物品有2种考虑
2.1. 不放入背包,此时价值就是i-1物品放入重量为j的背包 dp[i][j] = dp[i-1][j];
2.2. 如果放入背包,此时价值为i-1物品放入j-w[i]背包的价值 + i物品放入的价值 dp[i][j] = dp[i-1][ j-w[i] ] + v[i];
2.3. 放还是不放呢?取决于哪种情况的价值更大,也就是 dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i]]+v[i])
注意:
-
每个物品只有1个,物品i要么不放(0) 要么放(1),也就是01背包问题
-
这种算法的核心就是: 自底向上,当前值由前面已知值推算出来
状态转移过程图表分析
纵向:表示物品i
横向:表示背包空间j
横向物品为0,也即是没有物品,背包不管空间多大,价值都为0
纵向背包空间为0,也就是背包放不下任何物品,价值都为0
纵向背包空间1
纵向背包空间2
同理推算剩下的纵向背包3-12
思考:背包里放了哪些物品呢?
从i=5,j=12单元格倒退,
dp[5][12] -> dp[4][8](没有放,丢弃) -> dp[3][8] -> dp[2][7] -> dp[1][3]
知道背包里放了物品 4,3,2,1
总价值为5+3+2+1=11
js实现
function packBag(N, C, w, v){
// 创建二维数组 (N+1) * (C+1)
let dp = new Array(N+1); // N+1行
for(let z = 0;z <= N; z++){ // C+1列
dp[z] = new Array(C+1);
}
// 初始化第0行和第0列初始值为0
for(let z=0; z<=C; z++){
dp[0][z] = 0;
}
for(let z=0; z<=N; z++){
dp[z][0] = 0;
}
// 计算1~N行到1~C列的数据,从列开始算
for(let j=1; j<=C; j++){
for(let i=1;i<=N; i++){
let notPackValue = dp[i-1][j];
if(w[i] > j){ // 不能放
dp[i][j] = notPackValue;
}else{
let packValue = dp[i-1][j-w[i]] + v[i];
dp[i][j] = Math.max(notPackValue,packValue);
}
}
}
console.log(`dp:`,dp);
return dp[N][C];
}
module.exports = {
packBag : packBag
};
测试数据 {N:5, C:12, w:[0,3,4,1,8,4], v:[0,1,2,3,4,5]}
运行结果
dp: [
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
[0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ],
[0, 0, 0, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3 ],
[0, 3, 3, 3, 4, 5, 5, 5, 6, 6, 6, 6, 6 ],
[0, 3, 3, 3, 4, 5, 5, 5, 6, 7, 7, 7, 8 ],
[0, 3, 3, 3, 5, 8, 8, 8, 9, 10, 10, 10, 11 ]
]
result: 11