偷东西的学问-背包问题
前置
不同问题求解的区别仅在与约束条件,即:
\(W\) 表示总容量,\(w_i\) 表示单位物品所需容量
背包问题(0-1背包问题)
假设你是个小偷,背着一个可装 4 磅东西的背包。 你可盗窃的商品有如下3件(摘自算法图解):
作为一名优秀的小偷,为了让盗窃的商品价值最高,该选择哪些商品呢?
很明显,小偷需要在满足背包容量要求下,选择价值总和最大的。
使用动态规划
先解决小背包(子背包)问题,再逐步解决原来的问题
-
状态转移
cell[i][j]
表示前i
种物品恰放入一个容量为j
的背包能获得的最大价值
coding
def knapsack(goods,volume):
dp = [[0]*(volume+1) for _ in range(len(goods))]
for j in range(1,volume+1):#初始化
if goods[0][1] <= j: #如果物品所占空间(重量)小于容量j
dp[0][j] = goods[0][0] #第一个物品的价值
for i in range(1,len(goods)):
for j in range(1,volume+1):
# 如果 物品i可以放下
if goods[i][1] <= j:
#当前物品价值 + 取当前物品后的剩余空间
dp[i][j] = max(dp[i-1][j],goods[i][0]+dp[i-1][j-goods[i][1]])
else:
dp[i][j] = dp[i-1][j]
return dp
goods = [[1500,1],[3000,4],[2000,3]]#价值 重量
volume = 4
knapsack(goods,volume)
结果
将吉他和笔记本电脑装入背包时价值最高,为3500美元。
在刚才的盗窃活动中,每次可以偷的东西都是一个完整的个体,对每个物品要么选择偷,要么不偷,所以称为 0-1背包问题
可以偷商品的一部分吗(完全背包问题)
假如你在杂货店行窃,可偷成袋的扁豆和大米,但如果整袋装不下,可打开包装,再将背包 倒满。在这种情况下,不再是要么偷要么不偷,而是可偷商品的一部分。如何使用动态规划来处 理这种情形呢?
假设有如下商品(每种商品无限多)可供选择。
略作思索,小偷发现在决定偷哪些东西时(有限空间内使偷到的东西价值最大), 动态规划 是一个不错的方法,因为:
从每种物品i
偷/不偷 (\(k_i \in \{0,1\}\))变为 偷多少单位 (\(k_i\in \{0,1,2,...,W/w_i\}\)),背包总容量 W
,第i
类物品占 \(w_i\) 的空间。
-
状态转移
\[cell[i][j] = max(cell[i-1][j],v[i]*k_i+cell[i-1][j-k_i*w[i]])\\k_i\in \{0,1,2,...,W/w_i\} \]
coding
def knapsack(w,v,volume):
dp = [[0]*(volume+1) for _ in range(len(w))]
for j in range(1,volume+1):
for k in range(j//w[0]+1):
dp[0][j] = max(dp[0][j],v[0]*k)
for i in range(1,len(w)):
for j in range(1,volume+1):
temp = 0
for k in range(j//w[i]+1):
temp = max(temp,v[i]*k+dp[i-1][j-k*w[i]])
dp[i][j] = max(dp[i-1][j],temp)
return dp
weight = [2,1,3] #重量
value = [5,2,4]#价值
volume = 7
knapsack(weight,value,volume)
结果
物以稀为贵(多重背包问题)
由于燕麦的营养价值比较高,所以只有一点点,可偷的商品受到了限制
每种商品只有部分可供选择 \(k_i<M_i\),但问题还是
偷多少单位 (\(k_i\in \{0,1,2,...,W/w_i\}\)),背包总容量 W
,第i
类物品占 \(w_i\) 的空间。
多重背包问题 相比 完全背包问题多了限制条件,即可偷物品的数量。
同样使用动态规划
- 状态转移\[cell[i][j] = max(cell[i-1][j],v[i]*k_i+cell[i-1][j-k_i*w[i]])\\k_i\in \{0,1,2,...,W/w_i\},k_i \leq M_i \]
coding
def knapsack(w,v,M,volume):
dp = [[0]*(volume+1) for _ in range(len(w))]
for j in range(1,volume+1):
for k in range(min(j//w[0],M[0])+1):#增加限制条件
dp[0][j] = max(dp[0][j],v[0]*k)
for i in range(1,len(w)):
for j in range(1,volume+1):
temp = 0
for k in range(min(j//w[i],M[i])+1):#增加限制条件
temp = max(temp,v[i]*k+dp[i-1][j-k*w[i]])
dp[i][j] = max(dp[i-1][j],temp)
return dp
weight = [2,1,3] #重量
value = [5,2,4] #价值
maxk = [1,3,3] #数量
volume = 7
knapsack(weight,value,maxk,volume)
结果