0-1背包问题学习

1:什么是0-1背包问题?

通俗来讲,一个背包能装下m千克重量得到物品,现在有n种商品供你选择,每个物品都有自己的重量和价值,并且每个物品的数量都为1,在不超出背包限制的条件下,能够装下的物品的最大价值和是多少?

2:情景模拟

针对上面的问题,我们给出以下情景:

商品信息表
商品名称商品价格商品重量
手机3000¥1kg
电脑6000¥4kg
ipad5000$2kg

背包容量为:6kg.

 3:思路分析

对于上述问题,我们可以发现,核心点在于:对于每个物品来讲,只有两种可能,装===不装。选择装,背包能不能装的下。装入该物品和不转入该物品,背包里面物品的价值和谁更大。

4:题解实战

我们可以依次对每个物品进行分析,建立如下的表格:

每一个中间表格就代表当前重量下背包的最大价值,对于列来说,一次往下形成商品的可选集合,比如第一行,只有手机可以选择,第二行就有手机和ipad可供选择,第三行就有手机,电脑,ipad可供选择,之所以这样设计,可以参照上面的核心点。

背包价值表
商品名称\背包当前容量0kg1kg2kg3kg4kg5kg6kg
手机
ipad
电脑

1:对于第一行来讲,当前只有手机可以选择,当背包容量为0的时候,什么东西也放不进去,因此,我这里直接把背包价值为0的这一列初始化为0.而当背包容量大于等于1的时候,手机可以放进去,因此,对手机所在行的初始化如下:

2:接下来进入到第二行,此时有ipad可供选择了。 那我们来分析以下,ipad的重量为2kg,对于背包重量为1kg来讲,是放不下ipad的,因此,他们的最大价值还是3000,而当背包重量为2kg的时候,重点来了,放不放入ipad?第一种情况,放。那么放入ipad的时候,空间就为0了,而最大价值变为了5000.5000>3000,因此我们选择放入ipad,移除手机。下一步,背包的的容量变为了3kg,还是放不放的问题,放,那么放入ipad以后,我们惊喜的发现,当前背包容量还剩余了1kg,因为我们限定了商品数量为1,因此当前背包的最大价值就是ipad的价值+在没有ipad之前的背包容量为1kg的最大值。也就是5000+3000=8000,而对于后面的4,5,6kg,同样以这个思路推导即可。结果如下:

3:接下来进入到第三行,也就是商品集合中加入了电脑。同样我们进行分析,电脑的重量为4kg,那么对于0-3kg来讲,是放不下电脑的, 最大价值就是上一行0-3kg的值,当来到4kg的时候,此时可以放下电脑,放入电脑,那么背包的剩余容量就为0,此时的价值就是6000,但是6000<8000,因此我们选择不放入。当背包容量来到5kg时,放入电脑,背包的剩余容量为1,因此当前的最大价值就是6000+ipad行里面背包容量为1的价值3000=9000,9000>8000,因此,我们在5kg里面放入电脑,接下来背包容量为6kg,我们同样采用5kg时的分析方法,那么背包的价值就是6000+ipad行容量为2的值5000=11000>8000,因此,在6kg的时候,我们选择放入电脑。结果如下图:

5:题解回顾及总结

通过上面的题解分析,我们已经得出了背包容量为6所能放入的最大价值为11000。

其实这就是动态规划的思想,把一个大问题,拆解成很多小问题,而每个小问题的解又依赖于前一个小问题的解,因此,我们只需要保证解决当前小问题的解是最优的,那么在解决下一个小问题时候,依赖于上一个小问题而得到的最优解一定是最优的。体现到本题就是:每次我要放入一个物品的时候,放入的后的价值就是:当前放入的物品价值+(当前背包总容量-当前放入物品的重量).最大价值  然后我们与上一行中未放入当前物品的最大价值比较,再决定是否放入即可。

动态规划推导:

其实在我的表格里面还应该再加入一行,那就是当商品选择为0的时候:

 

 我们就初始化一个行为手机个数+1,列为背包容量+1的二维数组:dp[i][j];他所表示的含义就是,在商品有i-0种选择,背包容量为j的时候,背包的最大价值。通过之前的推导,我们可以进行如下显示化赋值:dp[0][0-(j-1)] = 0,dp[0-(i-1)][0]  = 0;然后每一行每一行的循环就行了,dp[i][j] = Math.max(dp[i-1][j],当前价值+dp[i-1][j-当前物品重量]),最后即可得到结果。

6:Java代码测试


public class Beibao {
    public static void main(String[] args) {
        Product product = new Product("手机",1,3000);
        Product product1 = new Product("ipad",2,5000);
        Product product2 = new Product("电脑",4,6000);
        List<Product> list = new ArrayList<>();
        list.add(product1);
        list.add(product);
        list.add(product2);
        System.out.println(Beibao.maxVaule(list, 6));
    }

    public static  Integer maxVaule(List<Product> products,Integer capacity){
        //定义二维数组
        int[][] dp = new int[products.size()+1][capacity+1];
        //循环
        for (int i = 1; i < products.size()+1; i++) {
            for (int j = 1; j < capacity+1; j++) {
                //看能否放得下
                if(j-products.get(i-1).weight <0){
                    //放不下,直接就是上一行
                    dp[i][j] = dp[i-1][j];
                }else {
                    //放得下,做比较
                    dp[i][j] = Math.max(dp[i-1][j],products.get(i-1).price+dp[i-1][j-products.get(i-1).weight]);
                }
            }
        }
        return dp[products.size()][capacity];
    }


}
@Data
@AllArgsConstructor
class  Product{
    String name;
    Integer weight;
    Integer price;
}

 与之前分析一致

7:二维数组压缩为一维数组

在上面的二维数组,还可以压缩为一位数组,因为我们发现,下一层的数组更新的时候是去依赖上一层的数组的,因此我们只需要定义一个一位数组即可,每到下一层的时候就去复用上一层的数组。

dp[j]=Math.max(dp[j],dp[j-currentWeight]+currentValue);

初始化为0即可,表示当前没有物品可选。

然后就是遍历,这里还是先物品,再背包。也只能这样。而对于背包的遍历顺序就有讲究了,只能是倒序遍历,我们来分析一下正序遍历会有什么问题:

在进行第二行遍历的时候,因为是一维数组,所以当背包容量4kg的时候,减去平板容量2kg=2kg,这个时候去加的不是手机那一行的3000,而是当前行的5000,相当于手机就被重复添加了,不符合手机只有一个。

 而逆序添加:

 就只保证物品只用一次。

核心点在于,当前是个一维数组,顺序遍历的话,后面的数据用前面的数据,而前面一轮的数据是在本轮修改过的,就不正确了,逆序遍历的话就可以保证前面的数据还是上一轮的正确数据。

所以这里可以引出完全背包,与0-1背包不同的点就在于完全背包里面,每个物品有无数个。所以一维数组的顺序遍历就是完全背包的解法

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值