背包问题(Knapsack Problem)是一个经典的优化问题,通常描述为给定一组物品,每个物品有重量和价值,要求将这些物品放入一个背包中,背包有一个最大承重限制,目标是使得背包中物品的总价值最大。
背包问题有不同的变种,最常见的有0/1背包问题、完全背包问题和分数背包问题。
1. 0/1背包问题
0/1背包问题是最经典的背包问题。在这个问题中,每个物品只能选择放入背包中,或者不放入背包中,即每个物品的选择是“0”或“1”,不可分割。
问题描述:
- 给定n个物品,每个物品有一个重量
w[i]
和一个价值v[i]
,背包的容量为W
。 - 目标是选择物品,使得放入背包中的物品的总价值最大,并且总重量不超过背包的最大容量。
动态规划解法:
定义 dp[i][j]
为前i个物品中,总重量不超过j的情况下的最大价值。
递推关系:
- 如果第i个物品不放入背包,
dp[i][j] = dp[i-1][j]
; - 如果第i个物品放入背包,
dp[i][j] = dp[i-1][j-w[i]] + v[i]
(注意要确保j >= w[i]
)。
最终解为 dp[n][W]
,即前n个物品中,总重量不超过W的最大价值。
代码示例:
def knapsack_01(weights, values, W):
n = len(weights)
dp = [[0] * (W + 1) for _ in range(n + 1)]
for i in range(1, n + 1):
for w in range(W + 1):
if w >= weights[i - 1]:
dp[i][w] = max(dp[i - 1][w], dp[i - 1][w - weights[i - 1]] + values[i - 1])
else:
dp[i][w] = dp[i - 1][w]
return dp[n][W]
# 示例
weights = [2, 3, 4, 5]
values = [3, 4, 5, 6]
W = 5
print(knapsack_01(weights, values, W)) # 输出最大价值
2. 完全背包问题
完全背包问题与0/1背包问题的主要区别在于,每个物品可以选择多次放入背包中,即一个物品的数量不限。
动态规划解法:
定义 dp[j]
为容量为j的背包可以装入的最大价值。初始化 dp[0] = 0
,其他值为0。
递推关系:
- 对每个物品i,遍历容量j,从小到大更新
dp[j]
:dp[j] = max(dp[j], dp[j - weights[i]] + values[i])
。
代码示例:
def knapsack_complete(weights, values, W):
n = len(weights)
dp = [0] * (W + 1)
for i in range(n):
for w in range(weights[i], W + 1):
dp[w] = max(dp[w], dp[w - weights[i]] + values[i])
return dp[W]
# 示例
weights = [2, 3, 4, 5]
values = [3, 4, 5, 6]
W = 5
print(knapsack_complete(weights, values, W)) # 输出最大价值
3. 分数背包问题
分数背包问题允许将物品分割,物品的价值与重量成正比。
贪心算法:
- 计算每个物品的价值/重量比
value[i] / weight[i]
; - 按照价值/重量比从高到低排序物品;
- 按顺序将物品放入背包,直到背包容量用完。
代码示例:
def knapsack_fractional(weights, values, W):
n = len(weights)
items = [(values[i], weights[i], values[i] / weights[i]) for i in range(n)]
items.sort(key=lambda x: x[2], reverse=True) # 按价值/重量比排序
total_value = 0
for value, weight, ratio in items:
if W <= 0:
break
if weight <= W:
W -= weight
total_value += value
else:
total_value += value * (W / weight)
break
return total_value
# 示例
weights = [2, 3, 4, 5]
values = [3, 4, 5, 6]
W = 5
print(knapsack_fractional(weights, values, W)) # 输出最大价值
总结:
- 0/1背包问题:动态规划求解,适用于物品只能选择一次的情况。
- 完全背包问题:动态规划求解,适用于每个物品可以选择多次的情况。
- 分数背包问题:贪心算法求解,适用于物品可以分割的情况。
这些算法的复杂度和实际使用时的效率会受到问题规模(如物品数量n、背包容量W)的影响,可以根据实际情况选择合适的解法。