0-1背包问题

0 / 1 背包问题(0 / 1 knapsack problem)

背包问题(Knapsack problem)是一种组合优化的NP完全问题。问题可以描述为:给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高。问题的名称来源于如何选择最合适的物品放置于给定背包中。

相似问题经常出现在商业、[组合数学],[计算复杂性理论]、[密码学]和[应用数学]等领域中。

也可以将背包问题描述为决定性问题,即在总重量不超过W的前提下,总价值是否能达到V

1、题目描述

假设商店中有如下3个商品,他们的重量和价格如下:

索引重量价值
011500
143000
232000

假如你是一个小偷,你有一个重量为4的包,每个商品只能偷一次,请问你怎么偷才会使得最后的价值最大?

2、分析

这种问题一般可以用动态规划很好地解决。但是如果我不用动态规划,而是用搜索所有情况来解决也可以,每个商品都有偷或不偷的选项,所以n个商品就有n^2种情况,所以用遍历的方法时间复杂度为O(n^2) n为商品的数量

现在我们假设B(k, w) 表示的是前k个商品,在背包容量为w的情况下能偷的最高价值

  • 当现在面对的第k个物品重量太重时:B(k, w) = B(k-1, w),代表我在多了一个物品的选择的情况下,仍然和没有这件物品时的选择一样,所以结果也一样(因为我偷不了或者我不偷的情况)

  • 当第k个物品的重量我可以接受时:B(k, w) = B(k-1, w - 这件物品的重量) + 这件物品的价值 代表我如果偷了这件物品,那剩下的w - 这件物品重量的空间可以容纳的最大价值就是在上一次选择时B(k-1, w - 这件物品的重量)的值。再加上这件物品的价值就是我偷了这件物品的最大值。

所以,在衡量一个B(k, w)时,首先看一下能不能偷,能得话看一下偷还是不偷两个的最大值,就是B(k, w)的值,所以我们回到上面的问题,问题的解就是B(2,4)的值

我们用**二维数组 dp[][]**来表示整个的过程

可选商品 \ 背包容量01234
0号商品(1,1500)01500150015001500
0 ~ 1号商品(4,3000)01500150015003000
0 ~ 2号商品(3,2000)01500150020003500

如图中加粗数字 1500代表的是在有前两个商品,背包容量为2时可以偷的最大价值为1500

图中加粗数字3000,即在有前2个商品,背包重量为4时,可以偷的最大价值为3000,这个数是这样算的:

  • 第二个商品(1号)重量为4,正好满足,如果偷的话所以价值为3000 + 0 = 3000
  • 如果不偷的话价值和只有1个商品,背包容量为4的价值一样,1500
  • 取最大值为3000

所以问题的关键就在构建这个二维数组

3、实现

/**
 * 时间复杂度:O(n * capacity) n为商品数量,capacity为包的大小
 * 空间复杂度:O(n * capacity) 可以优化为capacity
 */
public class Main{
	
	/**
     * 0/1 背包问题
     * @param w w[i]代表i号物品的重量(从0开始)
     * @param v v[i]代表i号物品的价值(从0开始)
     * @param capacity 代表包的最大容量
     * @return 可以偷的商品的最大值
     */
    public static int knapsack(int[] w, int[] v, int capacity){
        int goods = w.length;	// 商品数
        int[][] dp = new int[goods][capacity + 1];

        // 初始化第一行,因为第一行上层没有元素了,即只有第一个商品时
        for(int j = 1; j <= capacity; j++){
            if(j >= w[0]) dp[0][j] = v[0];
        }
		
        // 前i个商品, 背包容量为j时偷得最大价值
        for(int i = 1; i < goods; i++) {
            for(int j = 1; j < capacity + 1; j++) {
                // 如果容量不够放下第i个商品
                if(w[i] > j) {
                    dp[i][j] = dp[i-1][j];
                } else { // 如果可以放下这件商品
                    dp[i][j] = 
                        Math.max(dp[i-1][j], v[i] +  dp[i-1][j-w[i]]);
                }
            }
        }
        
        // System.out.println(Arrays.deepToString(dp));
        return dp[goods - 1][capacity];
    }
}

用滚动数组优化空间复杂度:

因为如果我们从后往前构建每一行,那上一行保留的就可以在构建时候用

/**
 * 时间复杂度:O(n * capacity) n为商品数量,capacity为包的大小
 * 空间复杂度:O(capacity)
 */
public class Main{
	
	/**
     * 0/1 背包问题
     * @param w w[i]代表i号物品的重量(从0开始)
     * @param v v[i]代表i号物品的价值(从0开始)
     * @param capacity 代表包的最大容量
     * @return 可以偷的商品的最大值
     */
    public static int knapsack(int[] w, int[] v, int capacity){
        int goods = w.length;	// 商品数
        int[] dp = new int[capacity + 1];

        // 前i个商品, 背包容量为j时偷得最大价值
        for(int i = 0; i < goods; i++) {
            for(int j = capacity; j > 0; j--) {
                // 如果能装下就更新,装不下就不更新(上一行的值)
                if(j - w[i] >= 0) {
                    dp[j] = Math.max(dp[j], v[i] + dp[j - w[i]]);
                }
            }
        }
        return dp[capacity];
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值