【题目描述】有n个重量和价值分别为wi,vi的物品。从这些物品中挑选出总重量不超过W的物品,求所有挑选方案中价值总和的最大值。
【限制条件】
- 1≤n≤100
- 1≤wi,vi≤100
- 1≤W≤10000
【输入样例】第一行为n值,第二行为n个w值,第三行为n个v值,第四行为W值。
4
2 1 3 2
3 2 4 2
5
【输出样例】W(选择第0、1、3号物品)
7
1. 先用最朴素的方法,针对每个物品是否放入背包进行搜索试试看。
n=int(input())
w=list(map(int,input().split()))
v=list(map(int,input().split()))
W=int(input())
# 递归,从第i个物品开始挑选总重小于j的部分
def rec(i,j):
if i==n: # 已经挑选完毕
return 0
elif j<w[i]: # 没法取,跳过
return rec(i+1,j)
else: # 挑选和不挑选都尝试一下
return max(rec(i+1,j),rec(i+1,j-w[i])+v[i])
print(rec(0,W))
2. 针对这个样例,rec的递归调用情况如图所示。可以看到,rec以(3,2)为参数调用了两次,浪费了一些时间。因此,我们可以通过记忆化数组,把第一次调用时的结果记录下来,以避免重复计算。
n=int(input())
w=list(map(int,input().split()))
v=list(map(int,input().split()))
W=int(input())
# 记忆化数组
dp=[[-1 for i in range(W+1)] for j in range(n+1)]
# 递归,从第i个物品开始挑选总重小于j的部分
def rec(i,j):
if dp[i][j]>=0:
return dp[i][j]
ans=-1 # 初始化
if i==n: # 已经挑选完毕
ans=0
elif j<w[i]: # 没法取,跳过
ans=rec(i+1,j)
else: # 挑选和不挑选都尝试一下
ans=max(rec(i+1,j),rec(i+1,j-w[i])+v[i])
dp[i][j]=ans # 记录结果
return ans
print(rec(0,W))
通过记忆化数组,时间复杂度从O(2^n)降到了O(nW)。但是在初始化数组时,要特别注意它的容量要足够大。
3. 接下来,我们将记忆化数组写成递推式。
如上所示,不用写递归函数,直接利用递推式将各项的值计算出来,简单地用二重循环也可以解决这一问题。这便是动态规划法(DP)。
n=int(input())
w=list(map(int,input().split()))
v=list(map(int,input().split()))
W=int(input())
# 记忆化数组
dp=[[0 for i in range(W+1)] for j in range(n+1)]
for i in range(n-1,-1,-1):
for j in range(0,W+1):
if j<w[i]:
dp[i][j]=dp[i+1][j]
else:
dp[i][j]=max(dp[i+1][j],dp[i+1][j-w[i]]+v[i])
print(dp[0][W])
动态规划法的复杂度也是O(nW),但是简洁了很多。解决问题时可以按照如上方法从记忆化搜索出发推导出递推式,熟练后也可以直接得出递推式。