01 背包问题:C++ 一维数组与二维数组

01 背包问题:C++ 一维数组与二维数组

01 背包是动态规划(DP)的经典问题。下面通过同一道题,分别讲解 二维数组写法和一维数组优化写法。

一、题目示例

有 3 件物品,背包容量为 4。每件物品有重量和价值,每件物品最多选择一次。

物品 重量 w 价值 v
物品 1 1 15
物品 2 3 20
物品 3 4 30

求背包最多能装入的最大价值。

最优方案:选择物品 1 和物品 2,总重量为 4,总价值为 35。

二、二维数组写法(基础版)

1. 状态定义

dp[i][j]

表示:只考虑前 i 件物品,背包容量为 j 时,能够获得的最大价值。

状态转移分为两种情况:

  • 不选第 i 件物品:dp[i-1][j]
  • 选择第 i 件物品:dp[i-1][j-w[i]] + v[i]

因此:

\[ dp[i][j]= \begin{cases} dp[i-1][j],&j<w_i\\ \max(dp[i-1][j],dp[i-1][j-w_i]+v_i),&j\ge w_i \end{cases} \]

2. 完整 C++ 代码


#include <iostream>
#include <algorithm>
using namespace std;

int main() {
    int n = 3;  // 物品数量
    int W = 4;  // 背包容量

    int w[4] = {0, 1, 3, 4};   // 重量
    int v[4] = {0, 15, 20, 30}; // 价值

    int dp[4][5] = {0};

    // i 表示正在考虑第 i 件物品
    for (int i = 1; i <= n; i++) {
        // j 表示背包容量
        for (int j = 0; j <= W; j++) {

            // 不选择第 i 件物品
            dp[i][j] = dp[i - 1][j];

            // 如果容量足够,可以考虑选择
            if (j >= w[i]) {
                dp[i][j] = max(
                    dp[i][j],
                    dp[i - 1][j - w[i]] + v[i]
                );
            }
        }
    }

    cout << dp[n][W] << '\n';

    return 0;
}

输出:

35

3. 二维数组如何理解?

以 dp[2][4] 为例:

它表示只考虑前两件物品,背包容量为 4 时的最大价值。

  • 不选物品 2:价值为 dp[1][4] = 15。
  • 选物品 2:剩余容量为 1,可以放物品 1,价值为 dp[1][1] + 20 = 35。

因此:

\[ dp[2][4]=\max(15,35)=35 \]

二维数组的优点是状态清晰,适合初学者理解状态转移过程。

三、一维数组写法(空间优化版)

二维数组的状态只依赖上一行:

\[ dp[i-1][j],\quad dp[i-1][j-w_i] \]

因此,可以使用一个一维数组保存状态,节省空间。

1. 状态定义

dp[j]

表示当前已经处理过的物品中,背包容量不超过 j 时的最大价值。

2. 完整 C++ 代码


#include <iostream>
#include <algorithm>
using namespace std;

int main() {
    int n = 3;
    int W = 4;

    int w[4] = {0, 1, 3, 4};
    int v[4] = {0, 15, 20, 30};

    int dp[5] = {0};

    for (int i = 1; i <= n; i++) {

        // 必须从大到小遍历容量
        for (int j = W; j >= w[i]; j--) {

            dp[j] = max(
                dp[j],
                dp[j - w[i]] + v[i]
            );
        }
    }

    cout << dp[W] << '\n';

    return 0;
}

输出:

35

3. 为什么必须倒序遍历?

这是 01 背包最重要的知识点。

假设当前物品重量为 2、价值为 3,背包容量为 4。

如果从小到大遍历:

  1. dp[2] 更新为 3。
  2. 接着更新 dp[4] 时,使用了刚更新的 dp[2]。
  3. 相当于把同一件物品使用了两次。

这样就变成了完全背包。

因此,01 背包必须:

for (int j = W; j >= w[i]; j--)

口诀:01 背包容量倒序,避免同一件物品重复使用。

四、二维数组与一维数组对比

比较项目 二维数组 一维数组
状态 dp[i][j] dp[j]
含义 前 i 件物品的最优值 当前处理阶段的最优值
空间复杂度 \(O(nW)\) \(O(W)\)
时间复杂度 \(O(nW)\) \(O(nW)\)
容量遍历 通常正序即可 必须倒序
优点 容易理解、方便还原选择方案 节省内存
推荐学习顺序 先学 再学

这里二维数组使用上一行进行状态转移,所以容量 j 可以从小到大遍历;一维数组则必须倒序,才能保留上一轮的状态。

五、交互练习:判断循环方向

问题:下面哪段代码是正确的 01 背包一维数组写法?

A. for (int j = W; j >= w[i]; j--)

B. `for (int j = w[i]; j

查看答案

六、竞赛中应该如何选择?

  • GESP、CSP-J 初学阶段: 先掌握二维数组,理解状态定义和状态转移。
  • 熟悉 DP 后: 掌握一维数组优化,这是更常用的写法。
  • 需要输出具体选择了哪些物品: 二维数组通常更方便回溯方案;一维数组也可以实现,但需要额外记录或设计回溯方法。

最后记住两条核心代码:

// 二维数组:使用上一行的状态
dp[i][j] = max(dp[i-1][j],
               dp[i-1][j-w[i]] + v[i]);

// 一维数组:容量必须倒序
dp[j] = max(dp[j],
            dp[j-w[i]] + v[i]);

两种写法解决的是同一个问题,区别在于一维数组通过倒序遍历,实现了空间优化。

posted @ 2026-10-10 17:33  kkman2000  阅读(8)  评论(0)    收藏  举报