算法-背包问题

01背包

问题描述:

有 N 件物品和一个最多能被重量为 W 的背包。第i件物品的重量是weight[i],价值是value[i]。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。

解题思路:

  1. 递归方程

对于第i件物品,如果装进背包,则最大价值为:

背包大小为(W - weight[i])时前(i - 1)件物品最大价值 + value[i]

如果不装进背包,最大价值为:

背包大小为 W 时前(i - 1)件物品的最大价值

这里涉及到两个维度,需要建立一个二维数组:dp[i][j],其含义为前i件物品在背包大小为j时的最大价值:

dp[i][j] = max(dp[i - 1][j - weight[i]] + value[i], dp[i - 1][j])

  1. 初始化

第一列表示背包大小为0时,第i件物品的最优解,显然都为0。

第一行表示第1件物品在背包大小不同时最大价值,即当 weight[0] >= j时,dp[0][j] = value[0]。

  1. 遍历

先遍历物品,再遍历背包(横向优先):

for (int i = 1; i <= N; i++) {
    for (int j = 1; j <= W; j++) {
        if (j < weight[i - 1]) {
            dp[i][j] = dp[i - 1][j];
        } else {
            dp[i][j] = Math.max(dp[i - 1][j - weight[i - 1]] + value[i - 1], dp[i - 1][j]);
        }
    }
}

会发现每一行的数据是可复用的,即多维数组可转换为1维数组。

递推公式转换为:

dp[j] = max(dp[j - weight[i]] + value[i], dp[j])

这里需要注意的是,dp[j - weight[i]] 是由 dp[i - 1][j - weight[i - 1]] 精简而来,即想复用上一行的 dp[j - weight[i]] 数据。如果从小到大遍历背包,就会导致 dp[j - weight[i]] 的值被本行数据覆盖掉,也就无法再复用上一行的数据了,因此这里需要从大到小遍历背包。

for (int i = 1; i <= N; i++) {
    for (int j = W; j >= weight[i - 1]; j--) {
        dp[j] = Math.max(dp[j - weight[i - 1]] + value[i - 1], dp[j]);
    }
}

完全背包

问题描述:

有 N 件物品和一个最多能背重量为 W 的背包。第i件物品的重量是weight[i],价值是value[i]。每件物品都有无限个(也就是可以放入背包多次),求解将哪些物品装入背包里物品价值总和最大。

解题思路:

  1. 递归方程

在01背包问题中,物品数量只有1个,只有两种状态:装入背包和不装入背包。适合创建二维数组来推导递归方程。

而完全背包问题中,物品数量有多个,存在多种状态。不合适创建多维数组来推导递归方程。

因此我们换一个角度,从背包大小来考虑这个问题。

当背包大小为 j 时,遍历物品数量,做如下操作:

1.背包大小减去第1个物品的大小,即 j - weight[0],此时最大价值为:

背包大小为 j - weight[0] 的最大价值 + value[0]

2.背包大小减去第i个物品的大小,即 j - weight[i],此时最大价值为:

背包大小为 j - weight[i - 1] 的最大价值 + value[i - 1]

3.上述步骤中取最大值即为背包大小为 j 时的最大价值,递推公式为:

dp[j] = max(dp[j - weight[i - 1]] + value[i - 1], dp[j])

其中i需要遍历所有物品。

与01背包一维递归公式对比可以发现,两者是相同的,区别在于:

01背包问题倒序遍历,完全背包问题是正序遍历。

  1. 初始化

dp[0] = 0。

  1. 遍历
for (int j = 1; j <= W; j++){
    for (int i = 1; j <= N; i++){
        if (j - weight[i] >= 0){
            dp[j] = Math.max(dp[j - weight[i - 1]] + value[i - 1], dp[j]);
        }
    }
}

你可能感兴趣的:(算法-背包问题)