01背包_动态规划图文详细解读

问题描述

有n件不相同的物品和一个背包,每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。

在这里插入图片描述

例:背包最大重量为4,物品为

重量价值
物品011500
物品143000
物品232000

问背包能背的物品最大价值是多少?


算法思想

这道题目我是跟着卡尔哥的《代码随想录》学习的,但是对于小白可能不太友好,下面是《算法图解》的案例解析,这个案例与下面具体编码部分由部分出入:

假设你是个小偷,背着一个可装4磅东西的背包,可盗窃的商品有如下3件,为了让盗窃的商品价值最高,你该选择哪些商品?

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3ZQcbagY-1688278569877)

动态规划思想:先解决小背包(子背包)问题,再逐步解决原来的问题,每个单元格计算的是从第一行到当前行(行对应商品)之间商品选择性放入后,当前容量背包(当前列对应容量)对应的最大价值

image-20230702000222444

开始填充背包,第一行是吉他行,意味着你将尝试将吉他装入背包。在每个单元格,都需要做一个简单的决定:偷不偷吉他?

因为当前只出现了吉他一个物品,而吉他是1磅,而4个背包的大小是1磅、2磅、3磅和4磅,足以放下这一个1磅的物品,所以(0,0),(0,1),(0,2),(0,3)均填入只放1个吉他的最大价值1500;

image-20230702001450819

第二行是音响行,可偷的商品有吉他和音响(当前你还不能偷笔记本电脑,而只能偷音响和吉他),1磅的背包放不下4磅的音响,故(1,0)与(0,0)相同,最大价值还是1500;2磅的背包放不下4磅的音响,故(1,1)与(0,1)相同,最大价值还是1500;3磅的背包放不下4磅的音响,故(1,2)与(0,2)相同,最大价值还是1500;

注意,4磅的背包可以放下4磅的音响,且价值大于1500的音响,故放入音响,价值3000,背包容量剩下4-4=0磅,不再放入吉他,与(0,3)相比,3000>1500,最大价值为3000;

image-20230702002620823

第三行是笔记本行,可偷的商品有吉他、音响和笔记本电脑,(2,0)、(2,1)两格由于不足以装下音响和笔记本,情况与(1,0)、(1,1)相同;背包容量为3磅时,可以放下3磅的笔记本,价值2000,背包容量剩下3-3=0磅,不再放入吉他,与(1,2)相比,3000>1500,最大价值为3000;

注意,4磅的背包可以像上面一格一样装入3000的音响,但我们先尝试装入笔记本,背包容量剩下4-3=1磅,此时1磅的容量可以看做一个子背包,笔记本已经放入背包了,这个子背包不受笔记本影响,而音响行正巧是笔记本还没有出现(即不考虑笔记本时已有物品的在1磅下最大价值,),(2,0)即我们想寻找的1磅背包产生的最大价值,所以总价值为2000+1500=3500;3500>3000,故填入3500

image-20230702004035501
计算每个单元格的公式如下

image-20230702004035501


动规五部曲

接下来按照动规五部曲详细分析这个题目:

dp数组含义

dp[i] [j]的含义,从物品0到物品i,任取物品放在容量为j的背包,其所产生的最大价值为dp[i] [g]。

dp数组存储的就是从第0行到当前行之间的所有物品,在各个子背包中,产生的最大价值是多少。



递推公式

不放物品i: dp[i-1] [j] 因为遍历到物品i对应的行时,物品i只有两个状态,取或者不取,当不取物品i时,dp[i] [j]值就等于上一行同样容量背包的最大价值。

放物品i: dp[i-1] [j-weight(i)]+value(i) 因为放入物品i时,dp[i] [j]的值就是当前商品的价value(i)加剩余空间( j-weight(i) )的价值,剩余空间的最大价值,就是商品0到商品i-1之间任取,既没放入商品i之前,剩余空间所对应子背包容量的最大价值。

当前容量背包只可能有两个结果,一个就是放物品i,另一个是不放物品i,所以就是在这两个公式之间取最大值,递推公式为:

dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1] [j - weight(i)] + value(i));


dp数组初始化

350bf2d05ccdcec88148bac1cee9fa5

DP数组下标应从零开始,这样当背包容量为零时,可以在数组中找到相应的位置。

对DP数组进行初始化时,当背包容量为零时,所对应的最大价值自然为0;然后要初始化第一行物品,方便下面的递推,当背包容量大于等于一时,放入物品0的最大价值都是1500。

其余方格的值,初始化为任意值均可,因为其值都是由正上方的方格的值或者**左上方方格的值(根据递推公式可以看出)**所递推来的的。



遍历顺序

遍历该数组时,自然使用两层for循环,一层遍历背包容量,一层遍历物品号,其实哪一个先遍历都无所谓。因为每一个方格里的值都是由正上方的格和其左上方的格所递推来的,哪一种遍历顺序均不影响其递推。


具体代码

public class BagProblem {
    public static void main(String[] args) {
        int[] weight = {1,4,3};
        int[] value = {1500,3000,2000};
        int bagSize = 4;
        testWeightBagProblem(weight,value,bagSize);
    }

    /**
     * 动态规划获得结果
     * @param weight  物品的重量
     * @param value   物品的价值
     * @param bagSize 背包的容量
     */
    public static void testWeightBagProblem(int[] weight, int[] value, int bagSize){

        // 创建dp数组
        int goods = weight.length;  // 获取物品的数量
        int[][] dp = new int[goods][bagSize + 1];

        // 初始化dp数组
        // 创建数组后,其中默认的值就是0
        for (int j = weight[0]; j <= bagSize; j++) {
            dp[0][j] = value[0];
        }

        // 填充dp数组
        for (int i = 1; i < weight.length; i++) {
            for (int j = 1; j <= bagSize; j++) {
                if (j < weight[i]) {
                    /**
                     * 当前背包的容量都没有当前物品i大的时候,是不放物品i的
                     * 那么前i-1个物品能放下的最大价值就是当前情况的最大价值
                     */
                    dp[i][j] = dp[i-1][j];
                } else {
                    /**
                     * 当前背包的容量可以放下物品i
                     * 那么此时分两种情况:
                     *    1、不放物品i
                     *    2、放物品i
                     * 比较这两种情况下,哪种背包中物品的最大价值最大
                     */
                    dp[i][j] = Math.max(dp[i-1][j] , dp[i-1][j-weight[i]] + value[i]);
                }
            }
        }

        // 打印dp数组
        for (int i = 0; i < goods; i++) {
            for (int j = 0; j <= bagSize; j++) {
                System.out.print(dp[i][j] + "\t");
            }
            System.out.println("\n");
        }
    }
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值