0-1背包:使用滚动数组时为何要逆序枚举

本文通过一个具体的0-1背包问题实例,详细解释了如何使用动态规划解决此类问题,并介绍了如何通过滚动数组来优化空间复杂度。

问题简述:有一背包,最大体积是10,有三个物品,体积分别是3,4,5,重量分别是4,5,6,求在不超过背包体积的前提下,所放物品的最大重量是多少。

答:最大重量是11,选择的物品是2和3,其体积是9,小于背包体积10


我们已经知道,对于0-1背包问题,我们可以使用动态规划进行解决。

定义f(i,j):把前i个物品装入体积为j的背包中的最大重量

那么:f(i,j)=max{f(i-1,j),f(i-1,j-v[i]+w[i]}


根据该方程,我们可以用下面的程序解决上述问题:

#include <iostream>
#include <string.h>
#include <algorithm>
#include <stdio.h> 
using namespace std;

int main()
{
	int max_v = 10;				// 背包最大体积 
	int v[4] = {0,3,4,5};		// 体积 
	int w[4] = {0,4,5,6};		// 重量 
	int f[11][11];
	
	memset(f,0,sizeof(f));
	
	// f(i,j):把前i个物品装到容量是j的背包中的最大总重量 
	// f(i,j)=max{f(i-1,j),f(i-1,j-v[i]+w[i]}
	// 一定要牢记:我们要计算的是:f(3,10)  !!!   什么意思?对我们来讲,11*11的数组,只有f(3,10)对我们来讲是有意义的,其它的空间能省则省。^_^ 
	for (int i=1; i<=3; i++)
	{	
		for (int j=0;j<=10; j++)
		{
			f[i][j] = f[i-1][j];		// 如果少了这句,当j<v[i]时,f[i][j]将不被更新,那么f[2][3]可就是0而不是4了。 
			if (j >= v[i])
			{
				f[i][j]= max(f[i-1][j],f[i-1][j-v[i]]+w[i]);
			}
		}
	}
	
	// 输出
	for (int i=0; i<=3; i++)
	{
		for (int j=0; j<=10; j++)
		{
			printf("%4d",f[i][j]); 
		}
		printf("\n"); 
	}
	
	printf("The only answer we need is %d\n",f[3][10]);
}


输出结果:



对这个答案我们满意吗?

如果从最终答案的角度来讲,可以接受,毕竟我们得出了想要的解。可是,当数据量很大的时候,比如有m个物品,背包体积是n,那么我们需要定义数组为f[m][n],其空间复杂度为O(m*n)。而诚如我们所说过的那样,我们仅仅想知道的是背包能装下的最大重量(不要求打印方案),即f[n][m]的值,那么,有必要申请n*m的数组吗?或者说,有无方法可以降低空间复杂度呢?答案是有的,即滚动数组。

观察上图,除最后一行外,其余数据是多余的.....

由f(i,j)=max{f(i-1,j),f(i-1,j-v[i]+w[i]},我们可以简化为f(j)=max{f(j),f(j-v[i])+w[i]}

程序如下:

#include <iostream>
#include <string.h>
#include <algorithm>
#include <stdio.h> 
using namespace std;

int main()
{
	int max_v = 10;				// 背包最大体积 
	int v[4] = {0,3,4,5};		// 体积 
	int w[4] = {0,4,5,6};		// 重量 
	int f[11];
	
	memset(f,0,sizeof(f));
	
	// f(j):容量是j的背包中的最大总重量 
	// f(j)=max{f(j),f(j-v[i]+w[i]}
	for (int i=1; i<=3; i++)
	{	
		for (int j=10;j>=0; j--)  // 此处要逆序枚举容量
		{
			if (j >= v[i])
			{
				f[j]= max(f[j],f[j-v[i]]+w[i]);
			}
		}
		// 输出
		for (int j=0; j<=10; j++)
		{
			printf("%4d",f[j]); 
		}
		printf("\n"); 
	}
	
	printf("The only answer we need is %d\n",f[10]);
}

输出结果:



为什么计算时要逆序枚举?计算顺序所决定!请看下图:



如当i=1时,f(10)=max{f(10),f(10-v[1])+w[1]}=max{f(10),f(7)+w[1]}=max{0,4}=4

即f(10)依赖的是f(10)和f(7)的值,需要注意的是,此时的f(10)和f(7)是i=0时的f(10)和f(7),如果使用二维数组存储,无需担心覆盖问题,对体积的枚举逆序或正序都可,但是如果使用一维数组,若正序枚举会先计算f(7),那么,再计算f(10)时,i=0时的f(7)已被覆盖矣!



动态规划背包问题分类与解决方案总结 背包问题是组合优化领域经典问题,核心目标为:给定容量限制的背包与若干带“消耗属性”(如重量、体积)和“价值属性”的物品,通过合理选物品使总价值最大化,且总消耗不超背包容量。按物品选取规则、约束条件可分为7类核心场景,以下从核心特点、数学模型、DP解法(含状态定义、转移方程)、优化技巧、适用场景展开详细总结。 一、0-1背包问题(基础核心) 1. 核心特点 每个物品仅能选或不选(二选一,无中间状态),不可重复选取、不可分割。 2. 数学模型 - 输入:n个物品,物品i的消耗(如重量) w_i 、价值 v_i ;背包最大容量 W 。 - 决策变量: x_i \in \{0,1\} ( x_i=1 选物品i, x_i=0 不选)。 - 目标函数:最大化总价值 \max \sum_{i=1}^n v_i x_i 。 - 约束条件:总消耗不超容量 \sum_{i=1}^n w_i x_i \leq W 。 3. DP解法 (1)二维DP(原始版,无空间优化) - 状态定义: dp[i][j] 表示前i个物品中选,背包容量为j的最大价值。 - 转移方程: dp[i][j] = \begin{cases} dp[i-1][j] & (j < w_i,无法选物品i) \\ \max(dp[i-1][j], dp[i-1][j-w_i] + v_i) & (j \geq w_i,选/不选物品i取最大值) \end{cases} - 初始化: dp[0][j]=0 (前0个物品,价值恒为0)、 dp[i][0]=0 (容量为0,无法选物品,价值恒为0)。 - 间复杂度: O(nW) ,空间复杂度: O(nW) 。 (2)一维DP(空间优化版,常用) - 核心逻辑:二维DP中 dp[i][j] 仅依赖 dp[i-1][\cdot] ,可压缩为一维数组,通过“逆序遍历容量”避免重复选同一物品。 - 状态定义: dp[j] 表示背包容量为j的最大价值。 - 转移方程: dp[j] = \max(dp[j], dp[j-w_i] + v_i) \quad (j从W逆序遍历至w_i) - 初始化: dp[0]=0 ,其余 dp[j]=0 (或负无穷,视是否允许“空选”调整)。 - 间复杂度: O(nW) ,空间复杂度: O(W) 。 4. 扩展:统计选法方案数 - 目标:统计“总消耗≤j且总价值达标”(或仅“总消耗=j”)的选法数量,不关注价值大小。 - 转移方程: dp[j] += dp[j-w_i] (j从W逆序遍历至w_i)。 - 初始化: dp[0]=1 (容量0,“不选任何物品”为1种方案),其余 dp[j]=0 。 5. 适用场景 物品不可重复、数量少(n≤1000)、容量适中(W≤1e4),如“物品选/不选的价值最大化”。 二、完全背包问题 1. 核心特点 每个物品可无限次选取(无数量限制),仅受背包容量约束。 2. 数学模型 - 输入、目标函数、约束条件同0-1背包,仅决策变量调整: x_i \in \{0,1,2,...\} (非负整数,可重复选)。 3. DP解法 (1)二维DP(原始版) - 状态定义:同0-1背包 dp[i][j] (前i个物品、容量j的最大价值)。 - 转移方程: dp[i][j] = \begin{cases} dp[i-1][j] & (j < w_i,无法选物品i) \\ \max(dp[i-1][j], dp[i][j-w_i] + v_i) & (j \geq w_i,选物品i后可继续选同一物品) \end{cases} - 间复杂度: O(nW) ,空间复杂度: O(nW) 。 (2)一维DP(空间优化版,常用) - 核心逻辑:允许重复选物品,故“正序遍历容量”(选当前物品后,后续容量可复用该物品)。 - 状态定义:同0-1背包一维版 dp[j] 。 - 转移方程: dp[j] = \max(dp[j], dp[j-w_i] + v_i) \quad (j从w_i正序遍历至W) - 初始化: dp[0]=0 ,其余 dp[j]=0- 间复杂度: O(nW) ,空间复杂度: O(W) 。 4. 数学优化(辅助技巧) 计算物品单位消耗价值 \frac{v_i}{w_i} ,优先选性价比高的物品,可快速得到近似最优解;但因容量可能无法整除消耗,需结合DP修正精确解。 5. 扩展:统计选法方案数 - 转移方程: dp[j] += dp[j-w_i] (j从w_i正序遍历至W)。 - 初始化: dp[0]=1 ,其余 dp[j]=0 。 6. 适用场景 物品可重复使用,如“硬币兑换(不限硬币数量,凑指定金额)”“材料无限,制作物品价值最大化”。 三、多重背包问题 1. 核心特点 每个物品有固定数量限制 c_i (最多选 c_i 个),介于0-1背包( c_i=1 )与完全背包( c_i→∞ )之间。 2. 数学模型 - 输入、目标函数、约束条件同前,决策变量调整: x_i \in \{0,1,...,c_i\} (选数≤物品上限)。 3. DP解法 (1)直接法(暴力枚举,不推荐) - 状态定义: dp[j] (容量j的最大价值)。 - 转移方程: dp[j] = \max(dp[j], dp[j-k \cdot w_i] + k \cdot v_i) \quad (0 \leq k \leq c_i,且j \geq k \cdot w_i) - 间复杂度: O(nW \sum c_i) ,效率极低,仅适用于 c_i 极小场景。 (2)二进制优化(常用,高效) - 核心逻辑:将物品数量 c_i 拆分为 \log_2 c_i 个“新物品”(如 c_i=5 拆为1、2、2, c_i=7 拆为1、2、4),新物品重量=原重量×拆分系数,价值=原价值×拆分系数,转化为0-1背包求解(新物品选/不选对应原物品选对应数量)。 - 转移方程:复用0-1背包一维DP方程( dp[j] = \max(dp[j], dp[j-w_{新}] + v_{新}) ,j逆序遍历)。 - 间复杂度: O(nW \log c_i) ,空间复杂度: O(W) ,适配多数场景。 (3)单调队列优化(极限优化,大数据量) - 核心逻辑:通过滑动窗口维护“容量j附近可选取的最优价值”,消除枚举k的冗余步骤,将间复杂度降至线性。 - 转移方程推导:对容量j按 j \mod w_i = r 分组(r=0,1,...,w_i-1),每组内 j = r + t \cdot w_i ,方程转化为: dp[r + t \cdot w_i] = \max(dp[r + (t-k) \cdot w_i] + k \cdot v_i) \quad (0 \leq k \leq \min(c_i, t)) 用单调队列维护 dp[r + (t-k) \cdot w_i] - (t-k) \cdot v_i 的最大值,直接取队首更新当前dp值。 - 间复杂度: O(nW) ,空间复杂度: O(W) ,适用于 W≥1e5 的大数据场景。 4. 扩展:统计选法方案数 - 二进制优化后复用0-1背包方案数方程,直接法复用枚举k的累加方程( dp[j] += dp[j-k \cdot w_i] )。 - 初始化: dp[0]=1 ,其余 dp[j]=0 。 5. 适用场景 物品有数量限制,如“商品限购、材料库存有限,选物品价值最大化”。 四、混合背包问题 1. 核心特点 同一问题中同包含0-1背包物品(选1次)、完全背包物品(选无限次)、多重背包物品(选有限次),需分类型处理物品。 2. 解法核心 - 分类适配策略: 10-1背包物品:直接用一维DP逆序遍历容量更新; 2. 完全背包物品:直接用一维DP正序遍历容量更新; 3. 多重背包物品:先二进制拆分(或单调队列优化),转化为0-1背包逆序遍历更新。 - 状态定义: dp[j] (容量j的最大价值),全局共用一个DP数组。 - 转移方程:按物品类型复用对应背包的一维转移方程,遍历顺序为“先遍历物品→再按类型遍历容量”。 3. 间复杂度 取决于各类物品占比,整体为 O(nW + n_多 W \log c_i) ( n_多 为多重背包物品数量),空间复杂度 O(W) 。 4. 适用场景 多类型物品共存,如“购物部分商品限购、部分不限购、部分仅一件”。 五、分组背包问题 1. 核心特点 物品按类别分组,每组内至少选0个、最多选1个物品(组间独立,组内互斥)。 2. 数学模型 - 输入:m组物品,第k组含 s_k 个物品,组内物品i的消耗 w_{k,i} 、价值 v_{k,i} ;背包容量 W 。 - 决策变量:每组内仅1个物品 x_{k,i}=1 (或全为0),其余 x_{k,i}=0- 目标函数: \max \sum_{k=1}^m \sum_{i=1}^{s_k} v_{k,i} x_{k,i} ,约束条件: \sum_{k=1}^m \sum_{i=1}^{s_k} w_{k,i} x_{k,i} \leq W 。 3. DP解法 - 核心逻辑:先遍历组→再逆序遍历容量(避免组内选多个物品)→最后遍历组内物品,按“选组内某物品/不选组内任何物品”更新DP。 - 状态定义: dp[j] (容量j的最大价值)。 - 转移方程: dp[j] = \max(dp[j], dp[j-w_{k,i}] + v_{k,i}) \quad (先遍历组k→j从W逆序至w_{k,i}→遍历组k内物品i) - 初始化: dp[0]=0 ,其余 dp[j]=0- 间复杂度: O(mW \bar{s}) ( \bar{s} 为每组平均物品数),空间复杂度 O(W) 。 4. 扩展:统计选法方案数 - 转移方程: dp[j] += dp[j-w_{k,i}] (遍历顺序同上,j逆序)。 - 初始化: dp[0]=1 ,其余 dp[j]=0 。 5. 适用场景 物品分组且组内互斥,如“考试选做题(每组1道,选1道或不选)、套餐选择(每组1个套餐,选1个或不选)”。 六、二维费用背包问题 1. 核心特点 物品需消耗两类资源(如重量+体积、重量+金额),背包对两类资源均有容量限制(如背包限重W、限体积V),分0-1型与完全型。 2. 数学模型(以0-1型为例) - 输入:n个物品,物品i消耗资源1 w_{i1} 、资源2 w_{i2} ,价值 v_i ;背包资源1容量 W 、资源2容量 V 。 - 目标函数: \max \sum_{i=1}^n v_i x_i ,约束条件: \sum_{i=1}^n w_{i1} x_i \leq W 、 \sum_{i=1}^n w_{i2} x_i \leq V , x_i \in \{0,1\} 。 3. DP解法 (1)二维DP(原始版) - 状态定义: dp[i][j][k] 表示前i个物品、资源1容量j、资源2容量k的最大价值。 - 转移方程(0-1型): dp[i][j][k] = \begin{cases} dp[i-1][j][k] & (j < w_{i1} 或 k < w_{i2}) \\ \max(dp[i-1][j][k], dp[i-1][j-w_{i1}][k-w_{i2}] + v_i) & (j \geq w_{i1} 且 k \geq w_{i2}) \end{cases} - 间复杂度: O(nWV) ,空间复杂度: O(nWV) 。 (2)二维滚动数组(空间优化版,常用) - 核心逻辑:压缩物品维度,用二维数组 dp[j][k] 表示资源1容量j、资源2容量k的最大价值,0-1型需“双逆序遍历资源”,完全型需“双正序遍历资源”。 - 转移方程(0-1型): dp[j][k] = \max(dp[j][k], dp[j-w_{i1}][k-w_{i2}] + v_i) \quad (j从W逆序至w_{i1},k从V逆序至w_{i2}) - 转移方程(完全型): dp[j][k] = \max(dp[j][k], dp[j-w_{i1}][k-w_{i2}] + v_i) \quad (j从w_{i1}正序至W,k从w_{i2}正序至V) - 初始化: dp[0][0]=0 ,其余 dp[j][k]=0- 间复杂度: O(nWV) ,空间复杂度: O(WV) 。 4. 适用场景 物品消耗多类资源,如“行李箱限重且限体积,选物品价值最大化”“项目限资金且限间,选任务收益最大化”。 七、背包问题通用总结 1. 核心共性 - 本质:通过DP存储“局部容量下的最优价值/方案数”,避免重复计算,核心是“状态定义+转移逻辑”; - 空间优化优先级:二维DP→一维滚动数组(单/多资源维度),优先压缩物品维度; - 间优化核心:减少冗余枚举(如二进制拆分消除k枚举、单调队列优化滑动窗口)。 2. 关键差异对比表 背包类型 物品选取规则 核心遍历顺序 核心优化技巧 间复杂度 核心场景 0-1背包1次或不选 容量逆序 一维滚动数组   物品不可重复 完全背包 无限次选 容量正序 一维滚动数组   物品可重复使用 多重背包 最多选 次 逆序(拆分后) 二进制拆分、单调队列   物品有数量限制 混合背包 0-1+完全+多重共存 分类型定顺序 分类适配 适配多类型复杂度 多规则物品共存 分组背包 组内最多选1个 组→逆序容量→组内物品 组内遍历控制   物品分组且组内互斥 二维费用背包 消耗两类资源,选1次/无限次 双资源逆序/正序 二维滚动数组   物品消耗多类资源 3. 解题步骤 1. 明确物品类型(选法规则、消耗资源数)与背包约束(容量/资源上限); 2. 定义DP状态(单/多维度,对应资源容量,明确存储含义:价值/方案数); 3. 推导转移方程(按“选/不选当前物品”拆分,适配选取规则); 4. 确定初始化值(按“空容量/空物品”场景赋值,方案数必设 dp[0...]=1 ); 5. 优化空间/间(优先一维数组,大数据量补二进制/单调队列优化); 6. 验证边界(如容量不足、物品数量为0的极端场景)。 通过以上分类与解法,可覆盖绝大多数背包问题场景,核心是掌握“选法规则→遍历顺序→优化技巧”的对应关系,灵活适配具体需求。 帮我补充修改
最新发布
11-26
<think> 我们正在撰写一个关于动态规划背包问题的分类与解决方案总结文档。根据用户需求,我们需要详细描述各类背包问题(0-1背包、完全背包、多重背包等)的内容、差异对比以及解题步骤。同,我们将参考提供的引用内容,但以用户需求为主。 文档结构建议: 1. 引言:介绍背包问题的背景和应用场景(参考引用[1]) 2. 分类:分别介绍0-1背包、完全背包、多重背包等问题的定义和特点 3. 差异对比:使用表格对比各类背包问题的特点 4. 解题步骤:详细描述动态规划解决背包问题的步骤(参考引用[3]中的三步法) 5. 代码示例:提供核心代码(使用Python,因为示例代码是Python) 6. 应用场景:举例说明背包问题在实际中的应用(参考引用[1]和[2]) 下面开始撰写文档内容: </think> ### 动态规划背包问题分类与解决方案总结 本文系统梳理背包问题的核心类型、解题框架及实践应用,重点对比各类问题的特性与解法差异。 --- #### 一、背包问题分类及定义 1. **0-1背包问题** - **定义**:物品不可分割,每种物品仅1件。 - 状态转移方程: $$dp[i][j] = \max(dp[i-1][j], dp[i-1][j-w_i] + v_i)$$ - 空间优化(一维数组): ```python for i in range(n): for j in range(W, w[i]-1, -1): dp[j] = max(dp[j], dp[j - w[i]] + v[i]) ``` - **应用**:资源择优分配(如投资选项目、行李收纳)[^1]。 2. **完全背包问题** - **定义**:物品无限供应。 - 状态转移方程: $$dp[i][j] = \max(dp[i-1][j], dp[i][j-w_i] + v_i)$$ - 空间优化(正向遍历): ```python for i in range(n): for j in range(w[i], W+1): dp[j] = max(dp[j], dp[j - w[i]] + v[i]) ``` - **关键差异**:同一物品可重复选取(vs 0-1背包的逆向遍历)[^3]。 3. **多重背包问题** - **定义**:物品数量有限制(第 $i$ 件物品最多 $s_i$ 个)。 - 优化方法: - **二进制拆分**:将 $s_i$ 拆分为 $1,2,4,...,2^k$ 的组合,转化为0-1背包- **单调队列优化**:$O(NW)$ 间复杂度。 4. **分组背包问题** - **定义**:物品分组,每组仅选1件。 - 状态转移: $$dp[j] = \max_{k \in \text{组}_i}(dp[j], dp[j - w_k] + v_k)$$ --- #### 二、核心解题步骤 1. **定义状态** 明确 $dp[i][j]$ 含义(如:前 $i$ 件物品放入容量 $j$ 背包的最大价值)。 2. **构建转移方程** - 0-1背包:`不选` 或 `选当前物品` - 完全背包:`不选` 或 `选多次` 3. **初始化边界** - $dp[0][j] = 0$(无物品价值为0- $dp[i][0] = 0$(背包容量为0) 4. **空间优化** - 0-1背包:逆向遍历防覆盖 - 完全背包:正向遍历允重复 --- #### 三、问题对比与场景适配 | 问题类型 | 物品限制 | 遍历顺序 | 典型场景 | |----------------|----------------|---------------|------------------------| | 0-1背包 | 每件仅1个 | 逆向遍历 | 投资决策、行李收纳 | | 完全背包 | 物品无限 | 正向遍历 | 硬币组合(找零问题) | | 多重背包 | 数量上限 $s_i$ | 二进制拆分 | 原材料配额生产 | | 分组背包 | 每组选1件 | 组内枚举 | 课程选修(多选一) | --- #### 四、实战代码示例(0-1背包空间优化) ```python def knapsack_01(weights, values, capacity): dp = [0] * (capacity + 1) for i in range(len(weights)): # 逆向遍历防止覆盖 for j in range(capacity, weights[i] - 1, -1): dp[j] = max(dp[j], dp[j - weights[i]] + values[i]) return dp[capacity] ``` --- #### 五、应用场景扩展 - **资源分配**:在预算约束下选择高收益项目(0-1背包)[^1]。 - **字符串处理**:编辑距离问题(与背包思想类似)[^2]。 - **组合优化**:动态规划作为核心方法,可延伸至最长公共子序列(LCS)等问题[^3]。 ---
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

kangwq2017

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值