【动态规划学习笔记】完全背包详解

完全背包

1.理论基础

标准的纯完全背包问题描述为:有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品都有无限件,求将哪些物品装入背包里物品价值总和最大。

那么类比于01背包,完全背包与之不同的地方就在于每件物品有无限件

那么跟01背包类似,完全背包也有两种实现形式:二维数组和滚动数组

2.二维数组形式

先用动态规划五部曲对完全背包问题进行推导

1.确定dp数组及其下标含义

dp[i] [j]代表背包容量为j,在下标为0~i的物品中任选,每件可以选多次,背包所装的最大价值(不一定装满)

2.确定递推公式

跟01背包相同

因为完全背包相较于01背包的唯一差别是一个物品可以选多次,所在代码实现差别就在细节上

有放与不放两种选择,

不放 dp[i] [j] = dp[i - 1] [j]

放 dp[i] [j] = d[i] [j - weight[i]] + values[i]

注意这里的dp[i]

其实01背包和完全背包二者的二维数组形式的唯一差别就是这个下标,dp[i]代表着可以重复选择一个物品

两者取最大的

所以递推公式为dp[i] [j] = max(dp[i] [j], dp[i] [j-weight[i]] + values[i])

3.初始化dp数组

由于dp[i] [j]是由它左边和上边的元素得到的,先初始化左边第一列和上边第一行,第一列容量为0时能背的价值为0,第一行容量大于等于第一个物品重量的初始化为第一个物品价值,小于则为0

4.确定遍历顺序

由于dp[i] [j]是由它左边和上边的元素得出的,按行遍历和按列遍

历都可以

5.举例推导dp数组

具体的代码实现(以Java为例):

public class TwoDimension {
    public static void main(String[] args) {
        int[] weight = {1, 3, 4};
        int[] values = {15, 20, 30};
        int bagWeight = 4;
        System.out.println(completeBag(values, weight, bagWeight));
    }
    public static int completeBag(int[] values, int[] weight, int bagSize) {
        int n = values.length;
        int m = bagSize;
        int[][] dp = new int[n + 1][m + 1];
        for(int j = m; j >= 0 && j >= weight[0]; j--) {
            dp[0][j] = values[0];
        }
        for(int i = 1; i < n; i++) {
            for(int j = 1; j <= m; j++) {
                if(j < weight[i]) dp[i][j] = dp[i - 1][j];
                else dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - weight[i - 1]] + values[i - 1]);//这块使用i-1并从1开始遍历
            }
        }
        return dp[n - 1][m];
    }
}
​

3.滚动数组(一维数组)形式

与上面的二维实现形式类似。先用动态规划五部曲对完全背包问题进行推导

1.确定dp数组及其下标含义

dp[j]代表背包容量为j时背包所装的最大价值(不一定装满)

2.确定递推公式

跟01背包相同

因为完全背包相较于01背包的唯一差别是一个物品可以选多次,所在代码实现差别就在细节上,二维数组的差异是递归中的一个下标,而一维数组形式的差异是遍历顺序

有放与不放两种选择,

不放 dp[j] = dp[j]

放 dp[j] = d[j - weight[i]] + values[i]

两者取最大的

所以递推公式为dp[j] = max(dp[j], dp[j-weight[i]] + values[i])

3.初始化dp数组

容量为0时能背的价值为0,其他的未处理初始化为0即可

4.确定遍历顺序

让我们回忆一下之前01背包一维形式:外层循环遍历物品,内层循环反序遍历背包,为什么反序?是为了避免重复选择一个物品情况的出现

完全背包是一个物品可以被选择无限次,那么我们使用逆向思维很容易就得到完全背包一维形式的内层循环是正序的

对于先遍历物品还是先遍历背包,不同于01背包只能先遍历物品,完全背包问题两种都可以,主要是因为完全背包的物品是可以重复选择的,dp[j]是根据j前面的数据进行计算的,确保前面都是经过计算有数据的即可

5.举例推导dp数组

具体的代码实现(以Java为例):

public class OneDimension {
    public static void main(String[] args) {
        int[] weight = {1, 3, 4};
        int[] values = {15, 20, 30};
        int bagWeight = 4;
        System.out.println(completeBag1(values, weight, bagWeight));
        System.out.println(completeBag2(values, weight, bagWeight));
    }
    //先物品
    public static int completeBag1(int[] values, int[] weight, int bagSize) {
        int n = values.length;
        int m = bagSize;
        int[] dp = new int[m + 1];
        for(int i = 0; i < n; i++) {
            for(int j = weight[i]; j <= m; j++) {
                dp[j] = Math.max(dp[j], dp[j - weight[i]] + values[i]);
            }
        }
        return dp[m];
    }
    //先背包
    public static int completeBag2(int[] values, int[] weight, int bagSize) {
        int n = values.length;
        int m = bagSize;
        int[] dp = new int[m + 1];
        for(int j = 0; j <= m; j++) {
            for(int i = 0; i < n; i++) {
                if(j >= weight[i])
                    dp[j] = Math.max(dp[j], dp[j - weight[i]] + values[i]);
            }
        }
        return dp[m];
    }
}

以上是我对于完全背包理论的总结,希望对大家有所帮助

你可能感兴趣的:(动态规划,java)