0/1背包问题(python)
思路可见这篇博客
问题分析
- 是否需要打印最佳路径?
- 状态转移矩阵是怎样的?
- 若1不要需要且2只与之前的前一次的状态有关(i-1),尤其是这种用列来表示每个物品的时候,可以考虑压缩矩阵
- 是否有其他约束如必须选择到几个物品,如果有,则dp的维度会增加,在dp维度2维以上时,优先考虑压缩矩阵,压缩某个维度的时候一定要保存上一次的状态(逆序)
- dp数组中的值是否会越界,比如物品的价值特别大?
步骤
- 构建dp数组
- 寻找最佳路径
dp数组
- dp数组结构: shape(len(背包容量),(len(物品数量)))
- dp[x][y]代表当前的容量y下,只考虑前x个物品所能获得的最大价值
- 初始化数组可以选择增加全0的一行一列,
数组压缩
- 压缩后一定要倒着更新数组,正着更新会覆盖掉上一次的状态
- dp[y] 等于前y个物品的最大价值,dp[x][y]在未更新之前记录的是上一次的状态,可以理解成dp[x-1][y]
- 打印循环中压缩数组可以发现其组成了压缩前的数组,换言之,数组压缩通过倒着更新数组的方式巧妙的利用了之前的状态,并且完成了更新.
- 压缩后无法找最佳路径.
- 更新前dp[i][j],更新后dp[i’][j’],当满足i’>i,j’>j即可逆序压缩,
在本例中dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - arr[i].w] + arr[i].v),i>i-1 j>[j - arr[i].w],这意味着当前值只跟他正上边,或者左上方的值有关
python代码实现
def find(i, j, dp, arr):
if i < 0:
return
if dp[i][j] == dp[i - 1][j]:
find(i - 1, j, dp, arr)
else:
print(i)
find(i - 1, j - arr[i].w, dp, arr)
def get_dp(cap, arr):
dp = [[0] * (cap + 1) for _ in range(len(arr))]
for i in range(1, len(dp)):
for j in range(1, len(dp[i])):
if arr[i].w > j:
dp[i][j] = dp[i - 1][j]
else:
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - arr[i].w] + arr[i].v)
return dp
def get_dp2(cap, arr):
dp = [0] * (cap + 1)
for i in range(1, len(arr)):
for j in range(cap, 0, -1):
if j - arr[i].w < 0:
break
dp[j] = max(dp[j], dp[j - arr[i].w] + arr[i].v)
print(dp)
return dp
if __name__ == "__main__":
import numpy as np
from collections import namedtuple
bag = namedtuple("bag", ["w", "v"])
arr1 = [bag(0, 0), bag(2, 3), bag(3, 4), bag(4, 5), bag(5, 6)]
cap = 8
print(arr1)
dp1 = get_dp(cap, arr1)
find(len(arr1) - 1, cap, dp1, arr1)
print(np.array(dp1))
dp2 = get_dp2(cap, arr1)
print("=" * 10)