算法学习|背包问题
问题描述
有n件物品,有容量为c的背包
每件物品占的容量为\(\omega_{i}\) ;价值为\(v_i\)
问在容量限制下怎样选择才能带走最大价值的物品
根据物品的选择,还可以分为以下几种类型:
1.0-1背包问题
2.完全背包问题
3.多重背包问题
4.混合背包问题
一、.0-1背包问题
每件物品至多选一次
基础递归解决
时间复杂度为 \(O(2ⁿ)\)
每个物品选择或不选择,返回两个选择中最大价值
def ks_recursion(n , c , weights , values):
if n == 0 or capacity == 0:
return 0
if weights[n-1] > capacity:
return knapsack_recursive(weights, values, capacity, n-1)
not_take = knapsack_recursive(weights, values, capacity, n-1)
take = knapsack_recursive(weights, values, capacity - weights[n-1], n-1) + values[n-1]
return max(not_take, take)
记忆递归
通过缓存中间结果,将时间复杂度优化为 \(O(n*c)\)
字典记录已经存在的计算结果,避免重复计算同一子问题
def ks_recursion_memory(n,c,w:list,v:list,memo:dict):
if n <0 or c <=0:#
return 0
if (n,c) in memo:
return memo[(n,c)]
no_taken = ks_loop(n-1,c,w,v,memo)#
take = 0#
if c >= w[n]:
take = ks_loop(n-1,c-w[n],w,v,memo) + v[n]#
memo[(n,c)] = max(take,no_taken)
return memo[(n,c)]
二维数组
复杂度:\(O(n * c)\)
对每个物品 i 和每个容量 w:
若当前物品重量 > 剩余容量:无法选择该物品,继承前 i-1 个物品的结果
dp[i][w] = dp[i-1][w]
若当前物品重量 ≤ 剩余容量:决策选或不选该物品,取最大价值。
dp[i][w] = max(
dp[i-1][w],
dp[i-1][w - weights[i-1]] + values[i-1]
)
最终结果在dp[n][c]
def ks_double_dim(n, c, w, v):
dp = [[0] * (c + 1) for _ in range(n + 1)]
for i in range(1, n + 1): # i:每个物品
for ca in range(1, c + 1): # c:每个容量
if w[i - 1] > ca:
dp[i][ca] = dp[i - 1][ca]
else:
dp[i][ca] = max(dp[i - 1][ca], dp[i - 1][ca - w[i - 1]] + v[i - 1])##
return dp[n][c]
一维数组
def ks_one(n,c,w,v):
dp = [0]*(c+1)
for i in range(n):
for ca in range(c,w[i]-1,-1):
dp[ca] = max(dp[ca],dp[ca-w[i]]+v[i])
return dp[c]
若想求恰好填满容量C时最大价值,dp[0]初始化为0,其它初始化为负无穷即可
二、完全背包问题
每件物品可以重复选取
def unbounded_knapsack(weights, values, capacity):
dp = [0] * (capacity + 1)
for w in range(1, capacity + 1): # 正序遍历容量
for i in range(len(weights)): # 遍历每个物品
if weights[i] <= w:
dp[w] = max(dp[w], dp[w - weights[i]] + values[i])
return dp[capacity]
复杂度O(n*c)
三、多重背包问题
直接暴力循环
第\(i\)个物品最大可选次数 \(k[i]\)
直接扩展完全背包复杂度\(O(n*k*W)\)
def multi_knapsack_naive(weights, values, counts, capacity):
dp = [0] * (capacity + 1)
for i in range(len(weights)):
for _ in range(counts[i]): # 将物品i拆分成k个独立物品,暴力循环
for j in range(capacity, weights[i]-1, -1): # 0-1背包
dp[j] = max(dp[j], dp[j - weights[i]] + values[i])
return dp[capacity]
n, m = map(int, input().split())
dp = [0] * (m + 1)
for i in range(1, n + 1):
v, w, c = map(int, input().split())
k = 1
while k <= c:
for j in range(m, k * v - 1, -1):
dp[j] = max(dp[j], dp[j - k * v] + k * w)
c -= k
k += k
if c > 0:
for j in range(m, c * v - 1, -1):
dp[j] = max(dp[j], dp[j - c * v] + c * w)
print(dp[m])
二进制拆分法
- 核心思想
将数量 \(k\) 分解为 \(2^0, 2^1, ..., 2^m, remaining\) 的组合
将 \(k\) 次选择的物品转换为 \(log₂k\) 个独立物品,每个新物品代表选择 \(2^m\) 个原物品 - 拆分步骤对每个物品$ i$,其数量为 \(s\):
初始化 \(base = 1\)(从 \(2^0\) 开始拆分)
循环拆分直到$ s >= base$
将 \(base\) 个物品 \(i\) 打包成一个新物品,重量为 \(base * weight[i]\),价值为 \(base * value[i]\)
处理剩余数量 \(s\)
复杂度:\(O(n·logk·W)\)
def multi_knapsack_optimized(weights, values, counts, capacity):
# 1. 二进制拆分生成新物品列表
new_weights = []
new_values = []
for i in range(len(weights)):
s = counts[i]
base = 1
while s >= base:
new_weights.append(weights[i] * base)
new_values.append(values[i] * base)
s -= base
base *= 2
if s > 0:
new_weights.append(weights[i] * s)
new_values.append(values[i] * s)
# 2. 转化为0-1背包问题
dp = [0] * (capacity + 1)
for i in range(len(new_weights)):
for j in range(capacity, new_weights[i]-1, -1): # 逆序更新
dp[j] = max(dp[j], dp[j - new_weights[i]] + new_values[i])
return dp[capacity]
四、混合背包问题
混合背包是上面三个背包问题的集大成者
可以考虑引入标记参数分类处理
def hybrid_knapsack(items, capacity):
"""
items格式: [(type, weight, value, count)]
type=0: 0-1背包 count = None
type=1: 完全背包
type=2: 多重背包 count
"""
dp = [0] * (capacity + 1)
for item in items:
typ, weight, value = item[0], item[1], item[2]
if typ == 0:
# 0-1背包
for j in range(capacity, weight - 1, -1):
dp[j] = max(dp[j], dp[j - weight] + value)
elif typ == 1:
# 完全背包
for j in range(weight, capacity + 1):
dp[j] = max(dp[j], dp[j - weight] + value)
elif typ == 2:
# 多重背包:二进制拆分后按0-1处理
count = item[3]
# 二进制拆分
base = 1
s = count
while s >= base:
w = weight * base
v = value * base
for j in range(capacity, w - 1, -1):
dp[j] = max(dp[j], dp[j - w] + v)
s -= base
base *= 2
if s > 0:
w = weight * s
v = value * s
for j in range(capacity, w - 1, -1):
dp[j] = max(dp[j], dp[j - w] + v)
return dp[capacity]

浙公网安备 33010602011771号