0-1背包和完全背包问题

一、0-1背包

有一个容量为 N 的背包,要用这个背包装下物品的价值最大,这些物品有两个属性:体积 w 和价值 v。

定义一个二维数组 dp 存储最大价值,其中 dp[i][j] 表示前 i 件物品体积不超过 j 的情况下能达到的最大价值,每件物品只能被添加一次。设第 i 件物品体积为 w,价值为 v,根据第 i 件物品是否添加到背包中,可以分两种情况讨论:

  • 第 i 件物品没添加到背包,总体积不超过 j 的前 i 件物品的最大价值就是总体积不超过 j 的前 i-1 件物品的最大价值,dp[i][j] = dp[i-1][j]。
  • 第 i 件物品添加到背包中,前i-1件物品总体积不超过j-w的最大价值再加上第i件物品的价值,dp[i][j] = dp[i-1][j-w] + v。

第 i 件物品可添加也可以不添加,取决于哪种情况下最大价值更大。因此,0-1 背包的状态转移方程为:                            

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

// W 为背包总体积
// N 为物品数量
// weights 数组存储 N 个物品的重量
// values 数组存储 N 个物品的价值
public int knapsack(int W, int N, int[] weights, int[] values) {
    int[][] dp = new int[N + 1][W + 1];
    for (int i = 1; i <= N; i++) {
        int w = weights[i - 1], v = values[i - 1];
        for (int j = 1; j <= W; j++) {
            if (j >= w) {
                dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - w] + v);
            } else {
                dp[i][j] = dp[i - 1][j];
            }
        }
    }
    return dp[N][W];
}

空间优化

 在程序实现时可以对 0-1 背包做优化。观察状态转移方程可以知道,前 i 件物品的状态仅与前 i-1 件物品的状态有关,因此可以将 dp 定义为一维数组,其中 dp[j] 既可以表示 dp[i-1][j] 也可以表示 dp[i][j]。此时:

                                       dp[j] = max(dp[j],dp[j-w]+v),1\leqslant j\leqslant W

因为 dp[j-w] 表示 dp[i-1][j-w],因此不能先求 dp[i][j-w],防止将 dp[i-1][j-w] 覆盖。也就是说要先计算 dp[i][j] 再计算 dp[i][j-w],在程序实现时需要按倒序来循环求解。

public int knapsack(int W, int N, int[] weights, int[] values) {
    int[] dp = new int[W + 1];
    for (int i = 1; i <= N; i++) {
        int w = weights[i - 1], v = values[i - 1];
        //逆序避免前一行的值被覆盖
        for (int j = W; j >= 1; j--) {
            if (j >= w) {
                dp[j] = Math.max(dp[j], dp[j - w] + v);
            }
        }
    }
    return dp[W];
}

二、完全背包

有一个容量为 N 的背包,要用这个背包装下物品的价值最大,这些物品有两个属性:体积 w 和价值 v。

定义一个二维数组 dp 存储最大价值,其中 dp[i][j] 表示前 i 件物品组合体积不超过 j 的情况下能达到的最大价值,每件物品都可以重复使用。设第 i 件物品体积为 w,价值为 v,根据第 i 件物品是否被使用,可以分两种情况讨论:

  • 第 i 件物品未被使用,总体积不超过 j 的前 i 件物品的最大价值就是总体积不超过 j 的只使用前 i-1 件物品组合的最大价值,dp[i][j] = dp[i-1][j]。
  • 第 i 件物品被使用,使用前i件物品组合总体积不超过j-w的最大价值再加上第i件物品的价值,dp[i][j] = dp[i][j-w] + v,此时dp[i][j]至少使用了一次第i件物品。

第 i 件物品可使用也可以不使用来进行组合,取决于哪种情况下最大价值更大。因此,0-1 背包的状态转移方程为:       

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

// W 为背包总体积
// N 为物品数量
// weights 数组存储 N 个物品的重量
// values 数组存储 N 个物品的价值
public int knapsack(int W, int N, int[] weights, int[] values) {
    int[][] dp = new int[N + 1][W + 1];
    for (int i = 1; i <= N; i++) {
        int w = weights[i - 1], v = values[i - 1];
        for (int j = 1; j <= W; j++) {
            if (j >= w) {
                dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - w] + v);
            } else {
                dp[i][j] = dp[i - 1][j];
            }
        }
    }
    return dp[N][W];
}

空间优化

 在程序实现时可以对完全背包做优化。观察状态转移方程可以知道,其实每一行单元的值的填写只要看它的左边的值。如果没有左边,它至少是上一行单元格的值。所以该状态方程可以转换为:

                                       \left\{\begin{matrix}dp[i][j] = dp[i-1][j],1\leqslant j< w \\ dp[i][j] = dp[i][j-w]+v, w\leqslant j\leqslant W \end{matrix}\right.

第一行表示该单元没有左边,所以取上一行单元格的值;第二行表示该单元值的填写只要看它的左边的值。如果不理解,建议将二维dp表格全部亲手填一遍。

因此可以将 dp 定义为一维数组,其中 dp[j] 既可以表示 dp[i-1][j] 也可以表示 dp[i][j]。此时:

                                       dp[j] = max(dp[j],dp[j-w]+v),w\leqslant j\leqslant W

public int knapsack(int W, int N, int[] weights, int[] values) {
    int[] dp = new int[W + 1];
    for (int i = 1; i <= N; i++) {
        int w = weights[i - 1], v = values[i - 1];
        //必须从w开始
        for (int j = w; j <= W; j++) {
             dp[j] = Math.max(dp[j], dp[j - w] + v);
        }
    }
    return dp[W];
}

三、对比

问题状态方程优化空间后的状态方程区别
0-1背包dp[i][j] = max(dp[i-1][j],dp[i-1][j-w]+v)dp[j] = max(dp[j],dp[j-w]+v),1\leqslant j\leqslant W「0 - 1」背包参考上一行,所以「0 - 1」背包 倒序填表
完全背包dp[i][j] = max(dp[i-1][j],dp[i][j-w]+v)dp[j] = max(dp[j],dp[j-w]+v),w\leqslant j\leqslant W「完全背包」参考当前行,所以完全背包正序填表

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值