问题描述
现有n个物品,它们有各自的体积和价值,现有给定容量的背包,如何让背包里装入的物品具有最大的价值总和?
代码实现
function knapsack(capacity,weight,value){
// 初始化一个 weight.length+1行 X capacity+1列 的二维数组
let dp = [];
for(let i=0; i<weight.length+1; i++){
dp[i] = [];
for(let j=0; j<capacity+1; j++){
dp[i][j] = 0;
}
}
// dp[i][j]表示当前背包容量为j,前i个物品最佳组合对应的价值
for(let i=1; i<weight.length+1; i++){
for(let j=1; j<capacity+1; j++){
if(j < weight[i-1]){
dp[i][j] = dp[i-1][j];
} else {
dp[i][j] = Math.max(dp[i-1][j],dp[i-1][j-weight[i-1]]+value[i-1])
}
}
}
// 最后一个值即为最大的价值总和
return dp[weight.length][capacity];
}
// 测试
let capacity = 8;
let weight = [2,3,4,5];
let value = [3,4,5,6];
console.log(knapsack(capacity,weight,value));
背包问题最优解回溯
通过上面的方法可以求出背包问题的最优解,但还不知道这个最优解由哪些商品组成,故要根据最优解回溯找出解的组成,根据填表的原理可以有如下的寻解方式:
- dp(i,j)=dp(i-1,j)时,说明没有选择第 i 个商品,则回到dp(i-1,j);
- dp(i,j)=dp(i-1,j-weight(i-1))+value(i-1)时,说明装了第 i 个商品,该商品是最优解组成的一部分,随后我们得回到装该商品之前,即回到dp(i-1,j-weight(i-1));
- 一直遍历到 i=0 结束为止,所有解的组成都会找到。
代码实现
function knapsack(capacity,weight,value){
// 初始化一个 weight.length+1行 X capacity+1列 的二维数组
let dp = [];
for(let i=0; i<weight.length+1; i++){
dp[i] = [];
for(let j=0; j<capacity+1; j++){
dp[i][j] = 0;
}
}
// dp[i][j]表示当前背包容量为j,前i个物品最佳组合对应的价值
for(let i=1; i<weight.length+1; i++){
for(let j=1; j<capacity+1; j++){
if(j < weight[i-1]){
dp[i][j] = dp[i-1][j];
} else {
dp[i][j] = Math.max(dp[i-1][j],dp[i-1][j-weight[i-1]]+value[i-1])
}
}
}
// 最后一个值即为最大的价值总和
console.log('最大价值为:', dp[weight.length][capacity]);
// 定义一个数组,长度为weight.length; 其中第i个数(下标为i-1)表示第i个商品是否被选中,选中为1,未选中为0
let item = [];
function detail(i,j){
if(i>0){
if(dp[i][j] === dp[i-1][j]){
item[i-1] = 0;
detail(i-1,j);
} else if(j-weight[i-1] >= 0 && dp[i][j] === dp[i-1][j-weight[i-1]]+value[i-1]) {
item[i-1] = 1;
detail(i-1,j-weight[i-1]);
}
}
return item;
}
console.log('商品组成:', detail(weight.length,capacity));
}
// 测试
let capacity = 8;
let weight = [2,3,4,5];
let value = [3,4,5,6];
knapsack(capacity,weight,value);
// 最大价值为: 10
// 商品组成: [ 0, 1, 0, 1 ]
END