为什么01背包要倒着推,完全背包要顺着推

为什么01背包要倒着推,完全背包要顺着推

我忽然发现,这个小学的知识点,额,一直没有弄懂。现在赶快总结一下。

01背包每个物品只能选一次,所以若用\(f[i][v]\)表示前i件物品,恰好放入一个容量为v的背包可获得的最大价值,那么其二维状态转移方程是:\(f[i][v]=max(f[i-1][v], f[i-1][v-c[i]]+w[i])\)。注意是恰好,所以最后还要max一下。也可以不max,那么定义要把恰好去掉,并且\(f[i][v]\)的初始值应当包括\(f[i][v-1]\)

显然01背包可以在空间上优化。采用滚动数组的思想,\(fnow[v]=max(fpre[v], fpre[v-c[i]]+w[i])\)。然而我们平时并不是这样写的,我们把fpre也优化掉了。通过倒推,我们可以让能够推出\(f[v]\)的状态比\(f[v]\)后更新,于是状态转移方程是\(f[v]=max(f[v], f[v-c[i]]+w[i])\)

完全背包的二维转移方程是这样的:\(f[i][v]=max(f[i-1][v], f[i][v-c[i]]+w[i])\)。也就是说自己可以用到自己那一层的状态。所以如果想把第一位优化掉,可以直接顺推。而优化空间后的方程除了推的方向,和01背包是一模一样的。

所以说,01背包和完全背包,之所以推的方向不同,是由于原来的二维方程不同。一维方程正好只是巧合而已。

转载于:https://www.cnblogs.com/MyNameIsPc/p/7782700.html

<think>我们参考用户提供的引用材料,特别是引用[1][2][3]中关于背包问题的描述,来详细解释动态规划解决背包问题的方法。 背包问题有多种类型,其中0-1背包是最基础的一种。下面我们将按照动态规划的解题步骤,详细说明如何解决0-1背包问题。 0-1背包问题描述: 给定n个物品和一个容量为C的背包。物品i的重量是w[i],价值是v[i]。每个物品只能选择放或不放(即0或1),求解将哪些物品放入背包可使这些物品的总重量不超过背包容量,且总价值最大。 动态规划解题步骤: 步骤1:定义状态 我们定义状态dp[i][j]表示考虑前i个物品(物品编号从1到i),在背包容量为j时,背包能装下的最大价值。 步骤2:状态转移方程 对于每个物品i,我们有两种选择:放入背包或不放入背包。 1. 如果不放入第i个物品,则问题转化为“前i-1个物品,容量为j”时的最大价值,即dp[i][j] = dp[i-1][j]。 2. 如果放入第i个物品,则问题转化为“前i-1个物品,容量为j-w[i]”时的最大价值加上当前物品的价值,即dp[i][j] = dp[i-1][j-w[i]] + v[i](注意:这里要求j>=w[i])。 因此,状态转移方程为: $$ dp[i][j] = \max(dp[i-1][j], dp[i-1][j-w[i]] + v[i]) \quad \text{当} j \geq w[i] $$ 如果j < w[i],则只能不放入,即dp[i][j] = dp[i-1][j]。 步骤3:初始化 考虑边界条件: - 当没有物品时(i=0),无论背包容量多大,最大价值都是0,即dp[0][j] = 0(0<=j<=C)。 - 当背包容量为0时(j=0),无论物品有多少,都无法放入,所以dp[i][0] = 0(0<=i<=n)。 步骤4:确定计算顺序 我们使用两层循环: 外层循环遍历物品(从1到n),内层循环遍历背包容量(从0到C)。这样,在计算dp[i][j]时,dp[i-1][j]和dp[i-1][j-w[i]]都已经被计算过。 步骤5:构造最优解 在计算出dp数组后,dp[n][C]就是最大价值。如果需要知道具体哪些物品被放入,我们可以从dp[n][C]开始回溯: - 如果dp[i][j] == dp[i-1][j],说明第i个物品没有放入,则回溯到dp[i-1][j]。 - 否则,说明第i个物品放入了,则记录该物品,并回溯到dp[i-1][j-w[i]]。 代码实现(二维DP): 注意:为了与物品编号对应,我们通常将物品数组索引从1开始,但实际编程中数组索引从0开始,因此需要调整。 下面给出一个具体的代码实现(使用0-indexed,但状态dp的i从0到n,容量j从0到C): ```python def knapsack_01(n, C, w, v): # 初始化一个(n+1)行(C+1)列的二维数组,初始值为0 dp = [[0] * (C + 1) for _ in range(n + 1)] # 填充dp数组 for i in range(1, n + 1): # 遍历物品,从第1个到第n个 for j in range(0, C + 1): # 遍历背包容量,从0到C if j < w[i - 1]: # 注意:w和v数组是0-indexed,所以第i个物品对应w[i-1]和v[i-1] dp[i][j] = dp[i - 1][j] else: dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i - 1]] + v[i - 1]) # 最大价值为dp[n][C] max_value = dp[n][C] # 回溯找选择的物品 selected = [] j = C for i in range(n, 0, -1): if dp[i][j] != dp[i - 1][j]: # 说明第i个物品被放入 selected.append(i - 1) # 添加物品索引(0-indexed) j -= w[i - 1] return max_value, selected[::-1] # 返回最大价值和选择的物品列表(按物品输入顺序) # 测试 n = 4 # 物品数量 C = 8 # 背包容量 w = [2, 3, 4, 5] # 物品重量 v = [3, 4, 5, 6] # 物品价值 max_value, selected = knapsack_01(n, C, w, v) print("最大价值:", max_value) # 输出:10 print("选择的物品索引:", selected) # 输出:例如 [0, 1, 2] 或 [0, 3] 等,具体取决于物品,但最大价值为10 ``` 注意:上述代码中,我们假设w和v数组都是0-indexed,即第一个物品的重量为w[0],价值为v[0]。在状态dp中,i=0表示没有物品,所以从i=1开始对应第一个物品。 空间优化(滚动数组): 观察状态转移方程,dp[i][j]只依赖于dp[i-1][j]和dp[i-1][j-w[i-1]],因此我们可以将二维数组优化为一维数组,但要注意内层循环需要逆序(从大到小)遍历背包容量,以避免覆盖上一行的状态。 优化后的一维DP代码: ```python def knapsack_01_optimized(n, C, w, v): dp = [0] * (C + 1) # dp[j]表示当前容量为j时的最大价值 # 为了回溯,我们可以另外记录选择,但一维DP下回溯比较麻烦,通常需要额外记录,这里先不回溯 for i in range(n): # 遍历物品,0-indexed # 内层循环逆序,从C到w[i](包括w[i]) for j in range(C, w[i] - 1, -1): # 不放入:dp[j]不变(即上一轮的值) # 放入:dp[j] = dp[j - w[i]] + v[i] if j >= w[i]: dp[j] = max(dp[j], dp[j - w[i]] + v[i]) max_value = dp[C] return max_value # 注意:这里没有记录物品选择,如果需要回溯,需要额外空间记录 # 测试 max_value = knapsack_01_optimized(n, C, w, v) print("最大价值:", max_value) # 输出:10 ``` 一维DP的空间复杂度为O(C),时间复杂度为O(n*C)。 总结: 动态规划解决背包问题的关键是定义状态和状态转移方程,然后注意初始化、计算顺序和空间优化。0-1背包背包问题的基础,其他背包问题完全背包、多重背包等都是在0-1背包的基础上进行变化。 引用说明: 本文参考了引用[1][2][3]中关于背包问题的动态规划解法,特别是0-1背包的状态定义和转移方程[^1][^2][^3]。 §§相关问题§§ 1. 完全背包问题与0-1背包问题在动态规划解法上有什么区别? 2. 如何用动态规划解决多重背包问题? 3. 背包问题有哪些常见的应用场景?</think>### 动态规划解决背包问题详解 背包问题是动态规划的经典应用场景,尤其0-1背包问题最能体现DP的核心思想。下面我将结合您提供的引用资料,详细解释解决步骤和实现方法。 --- #### 一、0-1背包问题定义 给定 $n$ 个物品和一个容量为 $C$ 的背包,物品 $i$ 的重量为 $w[i]$,价值为 $v[i]$。每个物品**只能选择放入或不放入**背包(0或1选择),求在不超过背包容量的前提下,能装入物品的最大总价值[^2][^3]。 **关键特征**: - 每个物品仅1件(不可分割) - 决策是二元的(放/不放) - 需要同时满足重量约束和价值最大化 --- #### 二、动态规划解决步骤 根据引用[2]中的DP解题框架,我们按以下步骤实现: ##### 1. 定义状态 - **$dp[i][j]$**:表示考虑前 $i$ 个物品,在背包容量为 $j$ 时能获得的最大价值 - **状态维度**:二维(物品数量 × 背包容量) - **物理意义**: - $i$:决策到第几个物品(1 ≤ $i$ ≤ $n$) - $j$:当前可用背包容量(0 ≤ $j$ ≤ $C$) ##### 2. 状态转移方程 核心逻辑:对于每个物品 $i$,考虑放或不放两种情况: ``` dp[i][j] = max(不放物品i, 放物品i) = max(dp[i-1][j], dp[i-1][j-w[i]] + v[i]) ``` 数学表达: $$ dp[i][j] = \begin{cases} dp[i-1][j] & \text{if } j < w[i] \\ \max\left(dp[i-1][j], dp[i-1][j-w[i]] + v[i]\right) & \text{if } j \geq w[i] \end{cases} $$ > **关键解释**: > - `不放物品i`:价值继承前 $i-1$ 个物品的结果 $dp[i-1][j]$ > - `放物品i`:前 $i-1$ 个物品在剩余容量 $j-w[i]$ 的最大值 + 当前物品价值 $v[i]$ ##### 3. 初始化边界条件 - 容量为0时:$dp[i][0] = 0$(任何物品都无法放入) - 物品数为0时:$dp[0][j] = 0$(没有物品可选) ##### 4. 填表顺序 双重循环: 1. 外层遍历物品:$i$ 从 1 到 $n$ 2. 内层遍历容量:$j$ 从 0 到 $C$ > **顺序重要性**:确保计算 $dp[i][j]$ 时,$dp[i-1][*]$ 已计算完成 ##### 5. 提取最优解 - 最大价值:$dp[n][C]$ - 具体方案:通过**回溯法**从 $dp[n][C]$ 物品选择 --- #### 三、完整代码实现(Python) ```python def knapSack(C, w, v, n): # 初始化DP表 (n+1)行×(C+1)列 dp = [[0] * (C + 1) for _ in range(n + 1)] # 填表 for i in range(1, n + 1): # 遍历物品 for j in range(C + 1): # 遍历容量 if w[i-1] > j: # 当前物品超重 dp[i][j] = dp[i-1][j] else: # 状态转移方程核心 dp[i][j] = max(dp[i-1][j], dp[i-1][j - w[i-1]] + v[i-1]) # 回溯法找具体方案 res = dp[n][C] selected = [] j = C for i in range(n, 0, -1): if res <= 0: break # 说明物品i被选中 if res != dp[i-1][j]: selected.append(i-1) # 记录物品索引 res -= v[i-1] # 扣除价值 j -= w[i-1] # 扣除重量 return dp[n][C], selected # 示例测试 if __name__ == "__main__": w = [2, 3, 4, 5] # 物品重量 v = [3, 4, 5, 6] # 物品价值 C = 8 # 背包容量 n = len(w) # 物品数量 max_value, items = knapSack(C, w, v, n) print(f"最大价值: {max_value}") # 输出:10 print(f"选中物品索引: {items}") # 输出:[0, 1, 2] 或 [1, 3] 等 ``` --- #### 四、关键点解析 1. **时间复杂度**:$O(n \times C)$ - 需填充 $n \times C$ 的DP表 - 当 $C$ 很大时可能效率低(可用滚动数组优化) 2. **空间优化(滚动数组)** 用一维数组替代二维数组: ```python def knapSack_optimized(C, w, v, n): dp = [0] * (C + 1) # 一维DP数组 for i in range(n): # 倒序遍历容量(避免覆盖未使用数据) for j in range(C, w[i]-1, -1): dp[j] = max(dp[j], dp[j - w[i]] + v[i]) return dp[C] ``` 3. **与其他背包问题的区别** | 问题类型 | 物品数量 | 状态转移特点 | |------------|---------------|--------------------------| | **0-1背包** | 每个物品1件 | 逆序更新容量 | | 完全背包 | 物品无限供应 | 正序更新容量 | | 多重背包 | 物品有限件数 | 增加数量循环 | --- #### 五、典型应用场景 1. 资源分配问题(如投资组合优化) 2. 裁剪问题(如布料切割) 3. 密码学中的子集和问题 4. 云计算中的任务调度[^1] > 动态规划的核心在于**将大问题分解为重叠子问题**,并通过存储中间结果避免重复计算。背包问题展现了DP如何优雅地处理带约束的优化问题[^2][^3]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值