背包问题(Knapsack Problem)是计算机科学和运筹学中最经典的组合优化问题之一。想象你是一名探险家,带着一个容量有限的背包进入藏宝洞,面对各种重量和价值不同的宝物,如何选择装入背包的物品才能使总价值最大化?这就是背包问题最直观的现实映射。
在实际工程中,背包问题的变体被广泛应用于资源分配、投资组合、任务调度等场景。比如云计算中的虚拟机部署、广告投放中的预算分配、工业生产中的原材料切割等,本质上都是背包问题的不同表现形式。
最基础的0-1背包问题定义如下:
其动态规划状态转移方程:
dp[i][j] = max(dp[i-1][j], dp[i-1][j-w_i] + v_i)
与0-1背包的区别在于:
特点:
以0-1背包为例的Python实现:
python复制def knapsack_01(W, wt, val):
n = len(val)
dp = [[0]*(W+1) for _ in range(n+1)]
for i in range(1, n+1):
for w in range(1, W+1):
if wt[i-1] <= w:
dp[i][w] = max(val[i-1] + dp[i-1][w-wt[i-1]], dp[i-1][w])
else:
dp[i][w] = dp[i-1][w]
return dp[n][W]
空间优化版本(一维数组):
python复制def knapsack_01_optimized(W, wt, val):
dp = [0]*(W+1)
for i in range(len(val)):
for w in range(W, wt[i]-1, -1):
dp[w] = max(dp[w], val[i] + dp[w - wt[i]])
return dp[W]
当物品数量较少时(n≤20),回溯法可能更高效:
python复制def backtrack(W, wt, val, index, current_w, current_v):
global max_value
if current_w <= W and current_v > max_value:
max_value = current_v
if index == len(wt) or current_w >= W:
return
# 不选当前物品
backtrack(W, wt, val, index+1, current_w, current_v)
# 选当前物品
if current_w + wt[index] <= W:
backtrack(W, wt, val, index+1, current_w + wt[index], current_v + val[index])
结合优先队列的优化解法:
python复制import heapq
class Node:
def __init__(self, level, profit, weight):
self.level = level
self.profit = profit
self.weight = weight
self.bound = 0
def bound(node, W, wt, val):
if node.weight >= W:
return 0
profit_bound = node.profit
j = node.level + 1
total_weight = node.weight
while j < len(wt) and total_weight + wt[j] <= W:
total_weight += wt[j]
profit_bound += val[j]
j += 1
if j < len(wt):
profit_bound += (W - total_weight) * val[j] / wt[j]
return profit_bound
def knapsack_branch_bound(W, wt, val):
pq = []
root = Node(-1, 0, 0)
root.bound = bound(root, W, wt, val)
heapq.heappush(pq, (-root.bound, root))
max_profit = 0
while pq:
_, u = heapq.heappop(pq)
if u.bound > max_profit:
level = u.level + 1
# 包含下一件物品
if u.weight + wt[level] <= W:
v = Node(level, u.profit + val[level], u.weight + wt[level])
if v.profit > max_profit:
max_profit = v.profit
v.bound = bound(v, W, wt, val)
if v.bound > max_profit:
heapq.heappush(pq, (-v.bound, v))
# 不包含下一件物品
v = Node(level, u.profit, u.weight)
v.bound = bound(v, W, wt, val)
if v.bound > max_profit:
heapq.heappush(pq, (-v.bound, v))
return max_profit
动态规划的空间复杂度可以从O(nW)优化到O(W):
当问题规模很大时,可以考虑:
某云平台需要将虚拟机部署到物理机上:
投资者有固定预算W,可选投资项:
原材料切割场景:
错误示例:
python复制dp = [[0]*W]*n # 浅拷贝问题!
正确做法:
python复制dp = [[0 for _ in range(W)] for _ in range(n)]
完全背包与0-1背包的内层循环方向不同:
当重量/价值为浮点数时:
当W很大时(如1e9):
约束条件扩展到多个维度:
物品属于不同组别,每组最多选一件:
物品间存在选择依赖关系:
测试环境:Python 3.8,Intel i7-10750H
| 方法 | n=20,W=100 | n=100,W=1000 | n=500,W=10000 |
|---|---|---|---|
| 基础DP | 0.001s | 0.12s | 6.34s |
| 空间优化DP | 0.0008s | 0.09s | 4.87s |
| 回溯+剪枝 | 0.003s | 超时 | 超时 |
| 分支限界 | 0.002s | 1.24s | 超时 |
| 贪心近似 | 0.0001s | 0.0002s | 0.0003s |