1,01背包和完全背包概念理解
2,01背包问题详解
(1)已知信息分析
已知条件:
-
n件物品
-
每件物品的重量数组
-
每件物品的价值数组
-
物品只能使用一次
求解答案: -
哪些物品放入背包使得背包中物品的价值最大
(2)例子题目
背包的最大重量为4
weight | value | |
---|---|---|
物品0 | 1 | 15 |
物品1 | 3 | 20 |
物品2 | 4 | 30 |
求背包可以放置最多价值的物品。
(3)解法
解法一:暴力解法
每件物品有两种状态,选或者不选,那么就可以使用回溯法搜索所有的情况。时间复杂度为o(2^n)
解法二:使用动态规划进行01背包问题
动态规划五部曲
-
dp数组定义以及下标的含义
-
确定递推公式
-
确定dp数组的初始值
-
确定遍历顺序
-
举例推导dp数组
首先根据例子题目进行推导 -
定义dp数组:dp[i][j]的含义就是从下标0-i中的物品任取一个,放进容量为j的背包的最大价值总量的值。
背包容量 0 | 背包容量 1 | 背包容量 2 | 背包容量 3 | 背包容量 4 | |
---|---|---|---|---|---|
物品0 | |||||
物品1 | |||||
物品2 |
- 确定递推公式
有两个方向可推出dp[i][j]
- 不放物品i的时候 dp[i-1][j]
- 放物品i的时候dp[i-1][j - weight[i]] + value[i]
上面的两个值取最大的一个来作为dp[i][j]的值
dp[i][j]= Math.max( dp[i-1][j],dp[i-1][j - weight[i]] + value[i])
- 初始化二维数组
- 从dp定义下手,若是j=0时,那意味着背包的容量为0,那物品无论是多少,那对应的dp[i][0]都是0.
- 从其他情况下手,在我们的递推公式中,i是由i-1推断出来的,因此必须初始化i=0的情况,当i=0的时候,只有j>=wight[i]的时候,该物品0才能够放进去。
初始化之后的结果就是:
背包容量 0 | 背包容量 1 | 背包容量 2 | 背包容量 3 | 背包容量 4 | |
---|---|---|---|---|---|
物品0 | 0 | 15 | 15 | 15 | 15 |
物品1 | 0 | 0 | 0 | 0 | 0 |
物品2 | 0 | 0 | 0 | 0 | 0 |
对于其他的数值,都是会经过计算然后覆盖掉的,所以就可以将其初始化为0。
- 确定遍历顺序
根据递推公式我们可以知道,dp[i][j]是由dp[i-1][j]或者dp[i-1][j - weight[i]] 来确定的,因此可以使用从前向后的遍历方式来进行。
关于物品和背包先遍历哪一个,这里是都可以的,但是先遍历物品比较好理解。
先遍历物品的理解是:将物品逐一的放在不同容量的背包里,求出dp数组中每一项的价值最大值。
-
举例推导数组
就是将你写好的递推公式,用人工的方式,表示出来,再将程序运行后的结果和你的画出来的表格进行对比,看是否一致,如果不一致就要检查其遍历顺序啦。 -
代码
// weight是物品重量的数组,value是物品的价值,size是背包的最大容量
var test = fucntion(weight,value,size){
let len = weight.length
//声明dp数组
let dp = new Array(len +1).fill(0).map(v=>new Array(size+1).fill(0))
// 初始化dp数组
for(let i = 0;i < len;i++){
dp[i][0] = 0
}
for(let j = 0; j <= size;j++){
if(j >= weight[0]){
dp[0][j] = weight[0]
}
}
// 递推公式
for(let i = 1; i < len;i++){
for(let j = 1; j <= size;j++){
if(j > weight[i]){
dp[i][j] = Math.max(dp[i-1][j],dp[i-1][j-weight[i]])
}
}
}
return dp[len][size]
}
解法三:动态规划精简做法。
对于解法二,是使用了二维数组,这儿可以精简为一维数组来进行计算。
-
dp数组的含义以及下标的含义
dp[j] 就是容量为j的背包所能装的最大价值。 -
声明递推公式
dp[j] 有两种选择,一个是dp[j - weight[i]],另一种是就是原始的dp[j],相对于二维数组中的dp[i-1][j]。
dp[j] = Math.max(dp[j],dp[j - weight[i]])
- dp数组的初始化
这里需要结合dp数组的定义来进行初始化值,首先dp[0]是容量为0 的背包里所能容纳的最大价值,那肯定就是0了。除了下标是0,其他的下标应该如何初始化呢?首先要看一下递推公式:
dp[j] = Math.max(dp[j],dp[j - weight[i]]),dp[j]要取最大的,那就可以将值初始化为正数的最小值0即可。
- 确定遍历顺序
对于一维数组来说,需要从后往前进行遍历,保证物品i只被放进去一次。
并且不可交换背包和物品的遍历顺序。
以上两点可以进行实验
- 代码
var test = function(weight,value,size){
let len = weight.length
//声明dp数组,并且初始化为0
let dp = new Array(size+1).fill(0)
//递推公式
for(let i = 0;i < len;i++){
for(let j = 0; j <= size;j++){
dp[j] = Math.max(dp[j],dp[j-weight[i]]+value(i))
}
}
return dp[size]
}
3,完全背包问题详解
(1)已知信息分析
已知条件:
- n件物品
- 每件物品的重量数组
- 每件物品的价值数组
- 物品可以使用多次(这是和01背包不同的地方)
求解答案:
- 哪些物品放入背包使得背包中物品的价值最大
(2)例子题目
和01背包中的题目一样。
(3)解法
类比于01背包中的一维数组的解法。
不同的是:
01背包中的核心代码,是先遍历物品,再遍历背包,并且背包是从大到小进行遍历的,是为了保证每个商品只能加一次。
对于完全背包来说,每个商品有无数个,因此需要从小到大进行遍历,并且先遍历物品或者背包都可以。
// 01背包
for(let i = 1;i < nums.length;i++){
for(let j = count;j >= nums[i];j--){
dp[j] = Math.max(dp[j],dp[j-nums[i]])
}
}
//完全背包
for(let i = 1;i < nums.length;i++){
for(let j = nums[i];j <= count;j++){
dp[j] = Math.max(dp[j],dp[j-nums[i]])
}
}