0-1背包

N件物品
w[ i ]:i-th件物品的重量 ( 重量 w = [1, 1, 2, 2] )
v[ i ]:i-th件物品的价值 ( 价值 v = [1, 3, 4, 5] )
背包容量W,使得价值最大,每个物品可以被使用0或者1次
记忆化递归

overlap:不同重量的组合可以组成其他重量。
把记忆化递归写成DP

- 二维DP
def backpack01(w, v, W, N):
dp = [[0] * (W+1) for _ in range(N)]
for i in range(N): # item:0th, [0th, 1th], [0th, ..., N-1th]
for j in range(W+1): # weight = 0, 1, ..., W
if w[i] <= j:
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i])
else: # 如果使用当前item就超重,所以取之前的item(i-1)的最大值
dp[i][j] = dp[i-1][j]
return max(dp[N-1]) # 所有item可能组合出来的最大value (0,1, ..., N-1)可以用
if __name__ == '__main__':
w = [1, 1, 2, 2]
v = [1, 2, 4, 5]
W = 4
N = 4
print(backpack01(w, v, W, N))
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i])
只考虑第 i 件item的策略:使用 ith or 不使用
使用前 i 个item占用空间为 j 时可以得到的最大value = max( 放入 i-th,不放入 i-th)
dp[i - 1][j]
不把 i-th放入背包,weight不变,价值 = 前 i - 1item的结果
dp[i - 1][j - w[i]] + v[i]
前i-1件物品放入剩下的容量为v-c[i]的背包中 + i-th的价值
- 优化为一维DP
dp[ i ]: 体积是 i 的情况下最大价值是多少
def backpack01(w, v, W, N):
dp = [0 for i in range(W + 1)]
for i in range(N): # item:0th, [0th, 1th], [0th, ..., N-1th]
for j in range(W, w[i] - 1, -1): # weight = 0, 1, ..., W
dp[j] = max(dp[j], dp[j - w[i]] + v[i])
return dp # 所有item可能组合出来的最大value (0,1, ..., N-1)可以用
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i])
dp[j] = max(dp[j], dp[j - w[i]] + v[i])
因为注意到二维数组dp实际上只有前后两层被用到dp[ i ][ ]和dp[ i - 1 ] [ ],所以将遍历倒叙,直接引入上下层保证 i 用的是 i - 1。
完全背包
有N种物品和一个容量为W的背包,每种物品都有无限件可用。
N件物品
w[ i ]:i-th件物品的重量 ( 重量 w = [1, 1, 2, 2] )
v[ i ]:i-th件物品的价值 ( 价值 v = [1, 3, 4, 5] )
背包容量W,不超过背包容量,使得价值最大。
从每种物品的角度考虑,与它相关的策略已并非取或不取两种,而是有取**0件、取1件、取2件……**等很多种
dp[ i ][ j ]=max( dp [ i-1 ][ v-k*w[ i ] ] + k * w[ i ], dp[ i -1] [ j ]) ……………k 为取多少件
但这样由于每个子问题求解的状态已经不是常数了,时间复杂度:O(Σ(W/w[i])*W)
dp[ i ]:体积是 i 的情况下,最大价值是多少。
def unbound_backpack(w, v, W, N):
dp = [0 for _ in range(W+1)]
for i in range(N): # 前 i 个数
for j in range(w[i], W + 1): # 从物品i的单个占用空间 ~ 空间上限循环:代表着这个物品数量的所有情况
# 从w[i]:一件 ~ W: W/w[i]件,这样完成了每件物品无限使用可能(不超过上限)的遍历
dp[j] = max(dp[j], dp[j - w[i]] + v[i])
return dp[-1]

多重背包
N件物品
W重量上限
i-th物品最多有si件,重量wi, 价值vi
dp[ i ]: 总体积 i 最大价值多少
一般解法–三重循环:
def multi_backpack(w, v, W, N):
dp = [0 for _ in range(W+1)]
for i in range(N): # 所占空间i
for j in range(W, w[i] - 1, -1): # 每个item(排除w[i] > = W的情况)
k = 1
while k <= s and k * v <= j: # i-th item使用k个
dp[j] = max(dp[j], dp[j -k * w[i]] + k * v[i])
k += 1
return dp[-1]
if __name__ == '__main__':
w = [1, 2, 3, 4] # 所占空间
v = [2, 4, 4, 5] # 价值
s = [3, 1, 3, 2] # 可用次数
W = 5 # 重量上限
N = 4 # 物品个数
print(unbound_backpack(w, v, W, N))
时间复杂度O ( n 3)
拆解为01背包:
把每个item拆成si个01物品
利用二进制进行分解,任何数可以表示成 2*k的组合。一个s分成log2(s)份
s - 1 - 2 - 4 - 8 -… 一直减到为负数,余数拿出来(10 - 1 - 2 - 4 = 3 --> 10 由 1 2 4 3 组成)
O( log (n) * n 2)
def multi_backpack(w, v, W, N, s):
dp = [0 for _ in range(W+1)]
goods = [] # 储存二进制划分的结果
for i in range(N):
k = 1
while k <= s[i]: # 开始进行二进制划分
s[i] -= k # 一点点逼近s,找到该物体可以有几个
goods.append([w[i] * k, v[i] * k])
k *= 2
if s[i] > 0: # 余数
goods.append([w[i] * s[i], v[i] * s[i]])
# 将二进制划分的结果进行01背包
for good in goods:
for j in range(W, good[0] - 1, -1):
dp[j] = max(dp[j], dp[j - good[0]] + good[1])
return dp[-1]
本文深入讲解背包问题的各种类型,包括0-1背包、完全背包及多重背包,并提供详细的算法实现,帮助读者理解如何通过动态规划解决这些经典问题。

5515

被折叠的 条评论
为什么被折叠?



