问题概述
有一个背包,最大承重为N,现在要装一些物品i(物品价值为value[i],物品重量为weight[i]),求这个背包装这些物品后的最大价值。
01背包特点
每种物品只有一个,要么选要么不选
01背包(二维数组版本)
分析
- dp[i][j]表示在背包容量j从i个物品中任选物品的最大价值dp[i][j]
- 价值全部先为0,考虑到后面有加法运算。第一行价值需要初始化,从第一行可以承受的重量开始到最后,对第一行第一个物品的价值初始化。
- 如果物品质量大于背包,说明背包放不下,此时最大价值应为
dp[i-1][j]
,即就是上一行同列的价值。同样的空间,但因为放不下,所以最大价值和上一行同列最大价值相等。 - 如果物品质量小于等于背包,说明背包放得下。现在分为两种情况,即放和不放。
- 如果不放,当前最大价值为上一行同列价值
dp[i-1][j]
- 如果放,当前最大价值为预留空间最大价值加物品的价值,即
dp[i-1][j-weight[i]]+ value[i]
- 遍历顺序这里任意,无论是先背包还是先物品都可以,习惯上一般先物品,再背包
代码
weight =[1,3,4]
value = [15,20,30]
Max = 4 # 背包的最大容量
dp = [[0]*5 for i in range(3)] # dp[i][j]表示i物品在j容量中的最大价值
for i in range(weight[0],Max+1):
dp[0][i] = value[0]
for i in range(1,3):
for j in range(1,5):
if weight[i] > j: # 如果物品质量大于背包,说明放不下
dp[i][j] = dp[i-1][j] # 和上一行最大价值一致
else:
dp[i][j] = max(dp[i-1][j],dp[i-1][j-weight[i]]+ value[i])
print(dp)
01背包(一维滚动数组版本)
分析
- 这里进行了状态压缩,由于我们知道二维数组我们其实只要最后一个值(即右下角的值),所以这耗费了大量的空间,为何我们不能只用一维数组滚动记录呢?
- dp[j]表示容量为j的背包中的最大质量
- 最大价值这里,第一个肯定是本身的价值
dp[j]
(其实这里相当于上一行同列的最大价值),以及预留空间价值加物品价值dp[j-weight[i]]+value[i]
取最大值。有人可能会觉得不太好理解,其实如果能理解二维数组操作的话,理解一维滚动数组应该不难。假如我第一行滚动完了,第二行初值不就是第一行的数值吗?现在就是我们不用刻意去写上一行,因为上一行和我这行是一样的,所以直接在二维的基础上去掉所有二维的行就可以了。 - 这里最要注意的一个应该是
遍历顺序
- 我们稍微思考一下,二维数组在操作的时候,由于是用上一行的数据更新本行最大值,所以背包可以用正序遍历,逆序遍历,这都无所谓。但是,一维滚动数组中,
背包一定需要逆序进行操作。
- 一维背包逆序的话,我当前背包最大价值的更新依赖于我前面的背包(由于前面背包还没有更新,所以相当于是用二维数组的上一行数据更新,符合我们刚才说的二维数组的操作)。如果是正序的话,正序背包最大值的更新依赖于前面的背包,前面的背包已经被更新过了,相当于我拿本行的数据进行更新(相当于叠加),某样物品会被重复取,不符合0,1背包!
代码
weight =[1,3,4]
value = [15,20,30]
Max = 4 # 背包的最大容量
dp = [0]*5 # dp[j]表示背包容量为j的最大价值为dp[j]
for i in range(3):
for j in range(4,weight[i]-1,-1):
dp[j] = max(dp[j],dp[j-weight[i]]+value[i])
print(dp)
最后结果
所以容量为4的背包,所能承受的最大价值都是35
如有错误,敬请指正,欢迎交流,谢谢♪(・ω・)ノ