背包问题 ---- 0/1 背包

1. 0/1背包介绍

n 个物品和一个容量为 m 的背包,第i件物品最多有 1 件,价值为 value[i], 重量为 weight[i],目标:使物品的价值最大,且重量总和不超过背包最大限制。

首先,介绍一下多重背包和完全背包的区别:

  • 多重背包:每个物品限制个数。
  • 完全背包:个数不受限制(喜欢多少拿多少),但是不能超过背包上限!

0/1背包是一种多重背包问题

假设:有三种物品:pineapple, applepen

item number value weight
pineapple 1 6 10
apple 1 7 5
pen 1 8 1

2. 解法

1) 递归

解决动态规划,一般可以从递归得到,这里先给出递归的代码:

public int dp(int i, int rest){
    // base case
    if (rest == 0 || i == 0){
        return 0;
    }
    if (weight[i] > rest){
        // i's weight larger than bag's rest weight, then not get item
        return dp(i-1, rest - weight[i]);
    }else
        // left is “not get item”, right is "get item"
        return Math.max(dp(i-1, rest),dp(i-1, rest -weight[i])+value[i]);
}

注意:weightvalue 数组的下标均从 1 开始

观察上述递归方程,可以发现:

  • dp[i-1][rest]:不取第i个物品

  • dp[i-1][rest-weight[i]] + value[i]: 取第i个物品

  • 可以发现状态转换方程可以这样写:
    d p [ i ] [ r e s t ] = M a t h . m a x ( d p [ i 1 ] [ r e s t ] , d p [ i 1 ] [ r e s t w e i g h t [ i ] ] + v a l u e [ i ] ) dp[i][rest] = Math.max(dp[i-1][rest],\quad dp[i-1][rest-weight[i]] + value[i])

2) 二维数组的动规

dp[i][v]表示前 i 个物品的最大价值

​ 转换方程见 公式(1)

public int process2(){
    int dp[][] = new int[n+1][m+1];
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            if (weight[i] > j){
                dp[i][j] = dp[i-1][j];
            }else{
                dp[i][j] = Math.max(dp[i-1][j],dp[i-1][j-weight[i]] + value[i]);
            }
        }
    }
    return dp[n][m];
}

3) 优化上述动规过程,转换为一维数组

由公式(1)可以看出,i 状态的数组可以由上一个 i-1状态的数组得到。所以我们可以将 i-1 -> i的转换用迭代的思想实现,从而只要保留 一维数组即可完成要求。

转换方程如下:
d p [ w ] = m a x ( d p [ w ] , d p [ w w e i g h t [ i ] ] + v a l u e [ i ] ) i [ 1 , n ] , w [ 0 , m ] dp[w] = max(dp[w], dp[w-weight[i]] + value[i]) \\ \qquad\qquad i \in [1,n], w \in [0,m]
废话不多说,先上代码

public int process3(){
    int dp[] = new int[m+1];
    for (int i = 1; i<=n;i++){
        for (int w = m; w>=0; w--){
            if (w<weight[i]){
                dp[w] = dp[w];
            }else{
                dp[w] = Math.max(dp[w],dp[w-weight[i]] + value[i]);
            }
        }
    }
    return dp[m];
}

看到代码可能会说,为什么内循环要从后往前?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6E5dAPqZ-1587618717697)(fig/e5e0877538092abae7b7bf3ed625ed5.jpg)]

先看一下如上的图片:

  • 可以看出 dp[w]dp[w-weight[i]]是由 i-1状态给出。
  • 公式左边的dp[w]i 状态。

如果从前往后,则会造成 公式中表示的ii-1 状态的混乱。

从而内循环需要从后往前,保证状态的一致性!

3. 测试用例

Input:

// 商品个数
int n = 5;
// 背包大小
int m = 8;
// weight and value
int []weight = new int[]{0,3,5,1,2,2};
int []value = new int[]{0,4,5,2,1,3};

Output:

10
发布了11 篇原创文章 · 获赞 0 · 访问量 58

猜你喜欢

转载自blog.csdn.net/weixin_44078008/article/details/105705382
今日推荐