0-1背包问题

定义

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

定义一个二维数组dp存储最大价值,其中dp[i][j]表示前i件物品体积不超过j的情况下能达到的最大价值。设第i件物品体积为w,价值为v,根据第i件物品是否添加到背包中,可以分两种情况讨论:
(1)第i件物品没添加到背包,总体积不超过j的前i件物品的最大价值就是总体积不超过j的前i-1件物品的最大价值,dp[i][j]=dp[i-1][j]
(2)第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)

/* N 为背包总体积
* M 为物品数量
* weights 数组存储 N 个物品的重量
* values 数组存储 N 个物品的价值
*/
public int bag(int N, int M, int[] weights, int[] values) {
    int[][] dp = new int[M + 1][N + 1];
    for (int i = 1; i <= M; i++) {
        int w = weights[i - 1], v = values[i - 1];
        for (int j = 1; j <= N; 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[M][N];
}

空间优化

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

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

int knapsack(int W, int N, vector<int> weights, vector<int> values) {
    vector<int> dp(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];
}

无法使用贪心算法的解释

0-1背包问题无法使用贪心算法来求解,也就是说不能按照先添加性价比最高的物品来达到最优,这是因为这种方式可能造成背包空间的浪费,从而无法达到最优。考虑下面的物品和一个容量为 5 的背包,如果先添加物品 0 再添加物品 1,那么只能存放的价值为 16,浪费了大小为 2 的空间。最优的方式是存放物品 1 和物品 2,价值为 22。
在这里插入图片描述

变种

(1)完全背包:物品数量为无限个
(2)多重背包:物品数量有限制
(3)多维费用背包:物品不仅有重量,还有体积,同时考虑这两种限制
(4)其它:物品之间相互约束或者依赖

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值