01 背包(从二维数组到一维滚动数组)

问题描述:

n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 每件物品只能用一次,求解将哪些)物品装入背包里物品价值总和最大。
在这里插入图片描述

二维dp数组01背包

1. 确定dp数组以及下标的含义

dp[i][j] 表示从下标为[0-i] 任意组合放入背包后,背包容纳重量为j时的最大价值

2. 确定递推公式

有两个方向推出来dp[i][j]:

  • 不放物品i:由dp[i - 1][j]推出,即背包容量为j,里面不放物品i的最大价值,此时dp[i][j]就是dp[i - 1][j]

    (其实就是当物品i的重量大于背包j的重量时,物品i无法放进背包中,所以背包内的价值依然和前面相同。)

  • 放物品i:由dp[i - 1][j - weight[i]]推出,dp[i - 1][j - weight[i]] 为背包容量为j - weight[i]的时候不放物品i的最大价值,
    那么dp[i - 1][j - weight[i]] + value[i](物品i的价值),就是背包放物品i得到的最大价值

    • j - weight[i] : 不装物品i时的容量
    • dp[i - 1][j - weight[i]] :装i时前j - weight[i]容量的最大价值。

i放进去时,那么这时候整个物品集就被分成两部分,1到i-1第i个,而这时i是确定要放进去的,那么就把j空间里的weight[i]给占据了,只剩下j-weight[i]的空间给前面i-1,那么只要这时候前面i-1j-weight[i]空间里构造出最大价值,即dp[i-1][j-weight[i]],再加上此时放入的i的价值value[i],就是dp[i][j]

递归公式 dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);

dp[i][j] = max(不放物品,放物品)

不装i即需要到前i-1个里面选,也就是前**i-1j背包容量下的最大价值**,由于前面都已经是最优解,直接取dp[i - 1][j]就是不装i条件下的最大价值

for (int i = 1; i < weight.size(); i++){
   	// 遍历物品
	for (int j = 1; j <= bagweight; j++){
   	// 遍历背包重量
		if (j < weight[i]) {
   	
			dp[i][j] = dp[i - 1][j];
		}
		else{
   	
			dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
		}
	}
}

为什么需要if (j < weight[i])?

因为:当j-weight[i] < 0时,数组会越界,即 dp[i - 1][j - weight[i]]会报错

3. dp数组如何初始化

dp[i][j]的定义出发:

如果背包容量j0的话,即dp[i][0],无论是选取哪些物品,背包价值总和一定为0。
如果i=0的情况就是只有

<think>嗯,用户想了解如何用滚动数组优化01背包问题的动态规划实现。首先,我得回忆一下01背包的基本动态规划方法。通常,01背包使用二维数组dp[i][j],其中i表示前i个物品,j表示背包容量,值表示最大价值。这样的话,空间复杂度是O(NV),N是物品数量,V是容量。 接下来,滚动数组优化的核心是降低空间复杂度。根据引用[3]提到的,dp[i][j]只依赖于i-1层的数据,所以可以只用一个一维数组来保存当前层的结果。这时候需要将内层循环倒序遍历,防止重复计算。比如,原来更新dp[i][j]时需要用到dp[i-1][j - w[i]],如果正序遍历的话,前面的值可能已经被覆盖了,导致错误。 然后,我得考虑如何将二维转为一维。状态转移方程原本是dp[i][j] = max(dp[i-1][j], dp[i-1][j - w] + v),用一维数组的话,每次更新j的时候,要确保用的是上一轮的数据。所以内层循环从大到小遍历,这样在计算j时,j - w的位置还没有被修改过,保持了上一轮的状态。 还要注意初始化的问题。一维数组初始化为0,因为当没有物品时,价值为0。然后逐个物品处理,每次处理时从最大容量开始,这样可以避免重复选取同一物品。 用户提供的引用[2]提到了一维数组的优化,步骤演示中展示了不同容量下的值变化,这可以帮助理解每一步的更新过程。例如,当处理第一个物品时,如果容量足够,就更新对应的位置。 可能用户还关心时间复杂度和优化后的效果。使用滚动数组后,空间复杂度从O(NV)降到O(V),而时间复杂度还是O(NV),所以主要优化了空间。 需要确保用户理解为什么必须逆序更新,否则可能会错误地允许物品被多次选取,变成完全背包问题。这一点在解释时要重点强调,避免混淆。 最后,可能需要给出具体的代码实现,比如Python的例子,并解释每一步的作用,这样用户能更直观地理解优化过程。同时,结合引用中的例子,说明滚动数组如何反复使用两个或单个数组来覆盖状态,但在这里01背包优化后只需要一个一维数组。</think>### 01背包问题的滚动数组优化实现 #### 1. 基础动态规划方法 标准的01背包问题动态规划使用二维数组$dp[i][j]$,其中$i$表示前$i$个物品,$j$表示背包容量,$dp[i][j]$表示前$i$个物品在容量$j$时的最大价值。状态转移方程为: $$dp[i][j] = \max(dp[i-1][j],\ dp[i-1][j-w_i] + v_i)$$ 时间复杂度$O(NV)$,空间复杂度$O(NV)$[^1]。 #### 2. 滚动数组优化原理 通过观察状态转移方程,发现$dp[i]$层计算仅依赖$dp[i-1]$层。因此可以将二维数组压缩为一维数组$dp[j]$,通过逆序遍历容量来保证状态更新的正确性[^3]。 优化后空间复杂度降为$O(V)$,关键步骤: 1. 初始化一维数组$dp[j] = 0$ 2. **逆序**遍历容量$j$(从$V$到$w_i$) 3. 状态转移方程更新: $$dp[j] = \max(dp[j],\ dp[j-w_i] + v_i)$$ #### 3. Python实现示例 ```python def knapsack_01(weights, values, capacity): n = len(weights) dp = [0] * (capacity + 1) for i in range(n): w, v = weights[i], values[i] for j in range(capacity, w-1, -1): # 逆序遍历 dp[j] = max(dp[j], dp[j - w] + v) return dp[capacity] ``` #### 4. 执行过程示例 以容量8的背包为例: ``` 初始状态:[0,0,0,0,0,0,0,0,0] 处理物品(2,3)后:[0,0,3,3,3,3,3,3,3] 处理物品(3,4)后:[0,0,3,4,4,7,7,7,7] 处理物品(4,5)后:[0,0,3,4,5,7,8,9,9] ``` 最终得到最大价值9[^2]。 #### 5. 关键点说明 - **逆序遍历必要性**:正序遍历会导致物品被重复选取,相当于完全背包问题 - **空间优化本质**:通过覆盖旧数据实现空间复用 - **边界处理**:当$j < w_i$时直接继承之前的值
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值