01背包问题另

问题:有 N 件物品和一个最多能承受重量为 W 的背包。第 i 件物品的重量为weight[i],得到的价值是value[i]。每件物品只能用一次,求解将哪些物品装入背包后物品的价值总和最大。
例题:
图示
这个题目中的物品不可以分割,要么装进包里,要么不装,不能说切成两块装一半。这也许就是 0-1 背包这个名词的来历。

动规标准套路

第一步要明确两点,「状态」和「选择」。

先说状态,如何才能描述一个问题局面?只要给定几个可选物品和一个背包的容量限制,就形成了一个背包问题,对不对?所以状态有两个,就是「背包的容量」和「可选择的物品」。

再说选择,也很容易想到啊,对于每件物品,你能选择什么?选择就是「装进背包」或者「不装进背包」嘛。

明白了状态和选择,动态规划问题基本上就解决了,只要往这个框架套就完事儿了:

for 状态1 in 状态1的所有取值:
    for 状态2 in 状态2的所有取值:
        for ...
            dp[状态1][状态2][...] = 择优(选择1,选择2...)

第二步要明确dp数组的定义。

dp数组是什么?其实就是描述问题局面的一个数组。换句话说,我们刚才明确问题有什么「状态」,现在需要用dp数组把状态表示出来。

首先看看刚才找到的「状态」,有两个,也就是说我们需要一个二维dp数组,一维表示可选择的物品,一维表示背包的容量。

dp[i][j]的定义如下:对于前i个物品,当前背包的容量为j,这种情况下可以装的最大价值是dp[i][j]。

比如说,如果 dp[3][5] = 6,其含义为:对于给定的一系列物品中,若只对前 3 个物品进行选择,当背包容量为 5 时,最多可以装下的价值为 6。

扫描二维码关注公众号,回复: 12643636 查看本文章

PS:为什么要这么定义?便于状态转移,或者说这就是套路,记下来就行了。

根据这个定义,我们想求的最终答案就是dp[N][W]。初始化就是dp[0][…] = dp[…][0] = 0,因为没有物品或者背包没有空间的时候,能装的最大价值就是 0。

细化上面的框架:

int dp[N+1][W+1]
dp[0][..] = 0
dp[..][0] = 0

for i in [1..N]:
    for w in [1..W]:
        dp[i][w] = max(
            把物品 i 装进背包,
            不把物品 i 装进背包
        )
return dp[N][W]

第三步,根据「选择」,思考状态转移的逻辑。

简单说就是,上面伪码中「把物品i装进背包」和「不把物品i装进背包」怎么用代码体现出来呢?

这一步要结合对dp数组的定义和我们的算法逻辑来分析:

先重申一下刚才我们的dp数组的定义:

dp[i][j]表示:对于前i个物品,当前背包的容量为j时,这种情况下可以装下的最大价值是dp[i][j]。

如果你没有把这第i个物品装入背包,那么很显然,最大价值dp[i][j]应该等于dp[i-1][j]。你不装嘛,那就继承之前的结果。

如果你把这第i个物品装入了背包,那么dp[i][j]应该等于dp[i-1][j-weight[i-1]] + value[i-1]。

首先,由于i是从 1 开始的,所以对value和weight的取值是i-1

而dp[i-1][j-weight[i-1]]也很好理解:你如果想装第i个物品,你怎么计算这时候的最大价值?换句话说,在装第i个物品的前提下,背包能装的最大价值是多少?

显然,你应该寻求剩余重量j-weight[i-1]限制下能装的最大价值,加上第i个物品的价值value[i-1],这就是装第i个物品的前提下,背包可以装的最大价值。

综上就是两种选择,我们都已经分析完毕,也就是写出来了状态转移方程,可以进一步细化代码:

for i in [1..N]:
    for w in [1..W]:
        dp[i][w] = max(
            dp[i-1][w],
            dp[i-1][w - weight[i-1]] + value[i-1]
        )
return dp[N][W]

最后一步,把伪码翻译成代码,处理一些边界情况。

用 Java 写的代码,把上面的思路完全翻译了一遍,并且处理了j - weight[i-1]可能小于 0 导致数组索引越界的问题:

public class BeiBao {
    
    
    public static void main(String[] args) {
    
    
        int[] weight = {
    
    1, 3, 4};
        int[] value = {
    
    15, 20, 30};
        int bagWeight = 4;
        
        int[][] dp = new int[weight.length + 1][bagWeight + 1];
        for(int i = 1; i <= weight.length; i++){
    
    
            for(int j = 1; j <= bagWeight; j++){
    
    
                if(j < weight[i - 1]){
    
    
                    dp[i][j] = dp[i - 1][j];
                }else{
    
    
                    dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weight[i - 1]] + value[i - 1]);
                }
            }
        }
        System.out.println(dp[weight.length][bagWeight]);
    }
}

一维dp数组:

public class BeiBao {
    
    
    public static void main(String[] args) {
    
    
        int[] weight = {
    
    1, 3, 4};
        int[] value = {
    
    15, 20, 30};
        int bagWeight = 4;
        
        int[] dp = new int[bagWeight + 1];
        dp[0] = 0;
        for(int i = 0; i < weight.length; i++){
    
    
            for(int j = bagWeight; j >= 0; j--){
    
    //每一个元素一定是不可重复放入,所以从大到小遍历
                if(j >= weight[i]){
    
    
                    dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
                }
            }
        }
        System.out.println(dp[bagWeight]);
    }
}

猜你喜欢

转载自blog.csdn.net/weixin_46497503/article/details/113397417