背包问题-01背包之滚动数组【动规五步法】

背包问题-01背包之滚动数组【动规五步法】

0 - 前言

本文是参考【代码随想录】大佬的【背包专题】,受益匪浅,上一个链接

动态规划:关于01背包问题,你该了解这些!(滚动数组)

1 - 滚动数组的由来

滚动数组的提出,简化了背包问题中dp数组的维度(由2维变为1维)

背包问题【01背包扫盲】【动规五步法】 中说到解决01背包问题的递推公式为:dp[i][j] = max(dp[i-1][j], dp[i-1][j-weight[i]] + value[i]])

仔细观察,如果将dp数组的第i-1行复制到第i行,那么递推公式就可以写成dp[i][j] = max(dp[i][j], dp[i][j - weight[i]] + value[i])。因为将dp数组i-1行复制到i行的操作并不影响weight数组和value数组,所以改写后的递推公式只影响了dp数组的行索引。

再进一步,既然递推式中的dp数组行索引i此时都完全相同,完全可以用一维dp数组表示,递推式变为:dp[j] = max(dp[j], dp[j - weight[i]] + value[i])

将二维dp数组上一行直接复制到下一行(即i-1状态的一维dp数组作为i状态的一维dp数组的初值),以这种逻辑更新一维dp数组,就像拉杆的老虎机,每次都会滚动更新,这就是滚动数组字面意思的由来。

2 - 动规五步法

使用动规五步法来分析滚动数组

1、确定dp数组以及下标含义
dp[j]表示容量为j的背包能装的最大价值

2、dp数组递推公式
首先一维数组dp[j]只有一个更新方向,那就是从j的上一状态j-weight[i]中更新出来,更新值为dp[j-weight[i]] + value[i]。需要注意的是,dp[j]虽然只有一个更新方向,但是他是有初值的(dp[i-1][j]复制过来的初值),因此需要在初值与更新值二者之中取最大。递推公式为:dp[j] = max(dp[j], dp[j-weight[i]] + value[i])

3、dp数组初始化
不同于一般的动态规划问题,滚动数组的一维dp数组不仅需要初始化刚开始的几个值,需要对所有位置都进行初始化。这是由滚动的原因造成的。因为题目中value都是正数,而递推公式每次要取最大值,所以dp[j] = 0。【代码随想录】大佬还提到,如果value存在负数,那么dp[j]就要全部初始化为负无穷。

4、dp数组遍历顺序
因为滚动的特性,我们必须在i-1状态将所有j全部遍历完,计算完所有的dp[j]才能进入到i状态进行复制,所以我们必须先遍历背包容量j,再来遍历物品i。【代码随想录】大佬友情提醒,这里在遍历背包容量时,与二维dp数组不同,一定要进行倒序遍历,即背包容量从大到小,这样是为了保证物品i只被放入一次。

5、举例演示dp数组

假设物品信息如下,背包容量为4

			重量		价值
物品0			1		 15
物品1			3		 20
物品2			4		 30
			dp[j]
  	  j 0  1   2   3   4
  i       			
物品00  15  15  15  15
物品10  15  15  20  35
物品20  15  15  20  35

大佬代码如下:

void test_1_wei_bag_problem() {
    
    
    vector<int> weight = {
    
    1, 3, 4};
    vector<int> value = {
    
    15, 20, 30};
    int bagWeight = 4;

    // 初始化
    vector<int> dp(bagWeight + 1, 0);
    for(int i = 0; i < weight.size(); i++) {
    
     // 遍历物品,实现了滚动逻辑
        for(int j = bagWeight; j >= weight[i]; j--) {
    
     // 遍历背包容量,注意是倒序遍历
            dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
        }
    }
    cout << dp[bagWeight] << endl;
}

int main() {
    
    
    test_1_wei_bag_problem();
}

猜你喜欢

转载自blog.csdn.net/weixin_44484715/article/details/115142389