完全背包问题


完全背包问题简介🐳

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

完全背包和01背包问题唯一不同的地方就是,每种物品有无限件


问题特征🤓

与01背包和多重背包不同的是,完全背包问题中的每个物品数量都是无限


思路转换😆

在01背包的滚动数组实现代码中,我们发现如果将bagSize的遍历按顺序遍历,而不是倒序,那么前面的dp[0]会被添加多次,这就是完全背包问题的解决方式


代码实现🚁

1、dp数组的含义:dp[i]表示bagSize=i时,背包中装入物品达到最大价值

2、递推公式:dp[j]=Math.max(dp[j],dp[j-weight[i]]+values[i])

3、数组的初始化:dp[0]=0,表示当bagSize=0时,背包中最大的价值为0,即装不下任何东西

4、遍历顺序:先遍历背包或者先遍历物品均可(但是求方法数量时,两者有较大区别)

5、测试一遍

//先遍历物品,再遍历背包
private static void testCompletePack(){
    int[] weight = {1, 3, 4};
    int[] value = {15, 20, 30};
    int bagWeight = 4;
    int[] dp = new int[bagWeight + 1];
    for (int i = 0; i < weight.length; i++){ // 遍历物品
        // 遍历背包容量!!!这里可以直接让j初始值为weight[i]
        for (int j = weight[i]; j <= bagWeight; j++){ 
            dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
        }
    }
    for (int maxValue : dp){
        System.out.println(maxValue + "   ");
    }
}

//先遍历背包,再遍历物品
private static void testCompletePackAnotherWay(){
    int[] weight = {1, 3, 4};
    int[] value = {15, 20, 30};
    int bagWeight = 4;
    int[] dp = new int[bagWeight + 1];
    for (int i = 1; i <= bagWeight; i++){ // 遍历背包容量
        for (int j = 0; j < weight.length; j++){ // 遍历物品
            if (i - weight[j] >= 0){
                dp[i] = Math.max(dp[i], dp[i - weight[j]] + value[j]);
            }
        }
    }
    for (int maxValue : dp){
        System.out.println(maxValue + "   ");
    }
}

拓展:关于完全背包求解决方法的 数量 问题😬

问题特征:

求装满背包有几种方法

解决方法:

这种问题我们不在将dp[j]的意义作为背包容量为j时可以放入的物品的最大价值,而是将其意义定义为背包容量为j可以装满背包的方法的数量!!!

值得注意的一点是,由于完全背包问题的遍历比较特殊,当两层for循环颠倒时,分别表示排列、组合情况下方法的数量,两者并不相同。

关于排列组合的解释

1、外层背包,内层物品:排列🥘

代码的实现:
//先遍历背包,再遍历物品
private static void testCompletePackAnotherWay(){
    int[] weight = {1, 3, 4};
    int[] value = {15, 20, 30};
    int bagWeight = 4;
    int[] dp = new int[bagWeight + 1];
    for (int i = 1; i <= bagWeight; i++){ // 遍历背包容量
        for (int j = 0; j < weight.length; j++){ // 遍历物品
            if (i - weight[j] >= 0){
                dp[i]+=dp[i-weight[j]];
            }
        }
    }
    for (int maxValue : dp){
        System.out.println(maxValue + "   ");
    }
}
关于对排列的解释🦈
/*
背包容量为5,有三个物品,分别重量分别为1,2,5。
按照排列的方式有多少种方法可以装满背包?
*/
bagSize = 5, weight = [1, 2, 5]

image

我们可以看到,在遍历bagSize=2之前,bagSize=1已经被完全遍历完了,同理,在遍历bagSize=3之前,2也被遍历完了。所以当遍历物品1、bagSize=3时,我们会将(2,1)添加进来,当遍历物品2、bagSize=3时,我们会添加(1,2),即bagSize=2时的排列的(2,1)和bagSize=3时排列的(1,2)都添加进来了,所以得到的方案数量是“排列”

2、外层物品,内层背包:组合🥘

代码的实现:
//先遍历物品,再遍历背包
private static void testCompletePack(){
    int[] weight = {1, 3, 4};
    int[] value = {15, 20, 30};
    int bagWeight = 4;
    int[] dp = new int[bagWeight + 1];
    for (int i = 0; i < weight.length; i++){ // 遍历物品
        // 遍历背包容量!!!这里可以直接让j初始值为weight[i]
        for (int j = weight[i]; j <= bagWeight; j++){ 
            dp[j]+=dp[j-weight[i]];
        }
    }
    for (int maxValue : dp){
        System.out.println(maxValue + "   ");
        }
	}
}
关于对组合的解释🦈
/*
背包容量为5,有三个物品,分别重量分别为1,2,5。
按照组合的方式有多少种方法可以装满背包?
*/
bagSize = 5, weight = [1, 2, 5]

image

我们可以发现,当遍历bagSize=3、物品1时,bagSize=2、物品2并没有遍历,导致(2)还没有被添加进dp[2]中,进而dp[3]中也不会有(2)。同理,当遍历bagSize=4、物品1时,bagSize=3、物品2并没有遍历,导致了此时的dp[3]中并没有(1,2),dp[4]中自然也不会添加进来(后续不会再出现dp[4]+=dp[3]的情况,因为物品1已经遍历过了,dp[4]+=dp[4-1]的情况不会再出现)。这样子就导致了没有排列的情况,所以得到的结果是”组合“

posted @ 2022-03-05 11:07  Jyang~  阅读(160)  评论(0)    收藏  举报