背包问题详解
一、问题引入:什么是背包问题?
背包问题是经典的动态规划问题,描述如下:
-
有一个容量为
m
的背包 -
有
n
个物品,每个物品有体积v
和价值w
-
目标:选择物品装入背包,使总价值最大且总体积不超过
m
-
限制:每个物品只能选一次(0-1背包)
动态规划解法
使用DP数组 f[i]
表示容量为 i
时的最大价值
三、C语言代码逐行解析
#include <stdio.h> #define N 1010 // 定义最大容量int f[N]; // DP数组,f[i]表示容量i时的最大价值int main() {int n, m;scanf("%d%d", &n, &m); // 输入物品数量和背包容量while(n--) { // 遍历每个物品int v, w;scanf("%d%d", &v, &w); // 输入物品体积和价值for(int i = m; i >= v; i--) { // 逆向更新DP数组int value = f[i - v] + w; // 选当前物品的新价值if(value > f[i]) f[i] = value; // 更新最大值}}printf("%d", f[m]); // 输出最大价值return 0; }
四、图解执行过程
输入示例:
3 5 // 3个物品,背包容量5 2 3 // 物品1:体积2,价值3 1 2 // 物品2:体积1,价值2 3 4 // 物品3:体积3,价值4
DP数组变化:
处理阶段 | f[0] | f[1] | f[2] | f[3] | f[4] | f[5] |
---|---|---|---|---|---|---|
初始状态 | 0 | 0 | 0 | 0 | 0 | 0 |
物品1 | 0 | 0 | 3 | 3 | 3 | 3 |
物品2 | 0 | 2 | 3 | 5 | 5 | 5 |
物品3 | 0 | 2 | 3 | 5 | 6 | 7 |
最优解:f[5] = 7(选择物品1和物品3)
五、为什么必须逆向更新?
-
正向更新会导致重复计算(变成完全背包问题)
-
逆向更新保证每个物品只被考虑一次
示例对比:
// 错误的正向更新(完全背包) for(int i = v; i <= m; i++) // 正确的逆向更新(0-1背包) for(int i = m; i >= v; i--)
六、复杂度分析
指标 | 暴力解法 | 动态规划 |
---|---|---|
时间复杂度 | O(2ⁿ) | O(n×m) |
空间复杂度 | O(n) | O(m) |
七、实际应用场景
-
资源分配问题
-
投资组合优化
-
裁剪问题
-
密码学中的子集和问题
八、常见变种问题
-
完全背包:物品可重复选(正向更新)
-
多重背包:物品有数量限制
-
分组背包:物品分组,每组只能选一个
-
依赖背包:物品间有依赖关系
九、优化与扩展
-
滚动数组优化:进一步减少空间复杂度
-
记录选择方案:增加路径记录数组
-
超大背包问题:当m很大时,改用价值作为DP维度