0/1背包问题是一个经典的组合优化问题,它可以描述为在给定一组物品和一个背包容量的情况下,选择哪些物品放入背包,以使得放入的物品总价值最大,且总重量不超过背包容量。
具体来说,问题可以描述如下:
- 给定n个物品,每个物品有一个重量(weight)和一个价值(value)。
- 给定一个背包容量(capacity)。
- 每个物品只能选择放入或不放入背包,不能切割。
- 目标是找到一种放置方式,使得放入背包的物品总价值最大,且总重量不超过背包容量。
数学表示为:
其中:
- 表示物品i的价值;
- 表示物品i的重量;
- 表示背包的容量;
- 表示物品i是否放入背包,取值为0或1。
0/1背包问题属于NP完全问题,其解决方案涉及到动态规划等算法。动态规划方法通常使用一个二维数组来记录在每个阶段(每个物品的考虑阶段)的最优解,通过填表的方式逐步求解问题。这种方法的时间复杂度为O(nW),其中n是物品数量,W是背包容量。
虽然0/1背包问题在最优子结构和重叠子问题方面具有动态规划问题的特点,但由于其状态空间较大,对于大规模问题,可能需要使用其他优化方法或近似算法。
参考题
Description
已知n个物体{1,2,3....n}与一个背包。物体i的重量为Wi > 0,价值为Pi > 0 (i=1,2,...n),背包容量为M > 0。
求在不超过背包容量的情况下,使得装进去的物体的价值最高。
Input
第一行为一个正整数N,表示有几组测试数据。 每组测试数据的第一行为两个整数n和M,0<n<=20, 0<M<100000. 再下去的n行每行有两个整数Wi和Pi, 0<Wi,Pi<10000.
Output
对于每组测试数据,输出一行,只含一个整数,表示装进去物体的价值最高值。
Sample Input
1
5 10
2 6
2 3
6 5
5 4
4 6
Sample Output
15
代码:
def knapsack(n, M, weights, values):
dp = [[0] * (M + 1) for _ in range(n + 1)]
for i in range(1, n + 1):
for j in range(1, M + 1):
if weights[i - 1] <= j:
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weights[i - 1]] + values[i - 1])
else:
dp[i][j] = dp[i - 1][j]
return dp[n][M]
# 示例输入数据写死在程序里
test_cases = 1
data = [
(5, 10, [(2, 6), (2, 3), (6, 5), (5, 4), (4, 6)])
]
for i in range(test_cases):
n, M, items = data[i]
weights, values = zip(*items)
result = knapsack(n, M, weights, values)
print(result)
结果:
15
关键解释:
在动态规划的解法中,dp[i][j] 表示在考虑前 i 个物品,且当前背包总容量为 j 的情况下,可以获得的最大价值。这里的 j 表示的是当前背包的总容量,而不是剩余容量。在考虑每个物品时,j 表示的是当前背包的总容量。
对于这个问题,我们是自底向上地填充一个二维数组 dp,其中 dp[i][j] 表示在考虑前 i 个物品,且当前背包总容量为 j 的情况下,可以获得的最大价值。在这个过程中,j 是逐渐变大的,表示我们逐步考虑越来越大的背包容量。
对于递推关系:
- 如果 weights[i - 1] <= j,表示当前物品 i 的重量不超过当前背包容量 j,那么我们可以选择装入物品 i 或不装入物品 i,取两者中价值的更大者。
1. dp[i - 1][j] 表示不装入物品 i,直接继承前 i-1 个物品时背包容量为 j 时的最大价值。
2. dp[i - 1][j - weights[i - 1]] + values[i - 1] 表示装入物品 i,当前总价值增加 values[i - 1],也就是说找得到一个大于等于0的数 j - weights[i - 1],因此dp[i - 1][j - weights[i - 1]]的值也是存在的。 - 如果 weights[i - 1] > j,表示当前物品 i 的重量超过了当前背包容量 j,那么我们不能装入物品 i,只能继承前 i-1 个物品时背包容量为 j 时的最大价值。
结论:
在整个过程中,我们填充了一个二维数组,最终 dp[n][M] 就是在考虑n个物品,且背包容量为 M 时的最大价值。