01背包问题

【题目描述】有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号物品)


 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),但是简洁了很多。解决问题时可以按照如上方法从记忆化搜索出发推导出递推式,熟练后也可以直接得出递推式。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值