C++01背包问题
一、01背包
概念:
给你一个容量为C的背包,有n个物品。每个物体都有两个属性,即重量(w)和价值(v)。如果背包剩余容量大于此物品的重量,即可放入,同时产生这个物品的价值。
问:如何将物体放入背包才能使放入物体的总价值最大,同时输出这个值?
为何不用贪心?
证明:
看到这个问题,你可能会想到使用贪心,我来反驳一下:
01 背包中,物品只能完整选择,选择某物品会占用背包空间,可能导致无法选择其他更有价值的物品组合。此时,贪心算法的 “局部最优”可能会排斥掉更优的全局组合。
举个反例:
背包最大承重为5,有3个物品,物品如下:
物品 | 重量 | 价值 | 性价比(≈) |
---|---|---|---|
A | 4 | 5 | 1.25 |
B | 3 | 4 | 1.33 |
C | 3 | 4 | 1.33 |
贪心(按单位价值排序): 先选择 B 或 C,无法容纳其他物品,总价值是4。
最优解:选择A,总价值为5。
科普:
有一种背包叫做分数背包问题,可以把某一种物品分割放入背包中,这时你就可以利用这种贪心方法。
dp做法:
1. 状态定义
设 dp[i][j]
表示:考虑前 i
个物品(编号从 1 到 i),且背包承重上限为 j
时,能获得的最大总价值。
- 其中,
i
的范围是0 ≤ i ≤ n
(i=0
表示无物品); j
的范围是0 ≤ j ≤ C
(j=0
表示背包承重为 0)。
2. 初始化
- 当
i=0
(无物品)时,无论背包承重j
多大,总价值均为 0,即dp[0][j] = 0
(0 ≤ j ≤ C
)。 - 当
j=0
(背包承重为 0)时,无论考虑多少物品,总价值均为 0,即dp[i][0] = 0
(0 ≤ i ≤ n
)。
3. 转移方程
对于第 i
个物品(重量 w[i]
,价值 v[i]
),有两种选择:
- 不放入:此时总价值与 “考虑前
i-1
个物品、承重j
” 的情况相同,即dp[i][j] = dp[i-1][j]
。 - 放入(需满足
j ≥ w[i]
):此时总价值为 “前i-1
个物品在承重j - w[i]
时的最大价值” 加上当前物品的价值,即dp[i][j] = dp[i-1][j - w[i]] + v[i]
。
因此,转移方程为:
if (j < w[i]) dp[i][j] = dp[i-1][j];
else dp[i][j] = max(dp[i-1][j], dp[i-1][j - w[i]] + v[i]);
4. 最终答案
考虑所有 n
个物品,且背包承重为 C
时的最大价值,即 dp[n][C]
。
5. 时间复杂度与空间复杂度
- 时间复杂度:
O(n*C)
,其中n
是物品数量,C
是背包最大承重。需遍历n*C
个状态,每个状态的计算为O(1)
。 - 空间复杂度:
- 原始二维数组:
O(n*C)
(存储n+1
行、C+1
列的状态)。
- 原始二维数组: