背包问题详解
一、问题引入:什么是背包问题?
背包问题是经典的动态规划问题,描述如下:
-  有一个容量为 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维度 
