可能你已经刷了很多01背包的题,但是真的对01背包领悟透彻了吗?,看我这一篇,使君对01背包的理解更进一步【代码+图解+文字描述】

news2024/9/24 5:26:04

一.概念理解:什么是01背包

关于01背包的概念理解如下:01背包是在M件物品取出若干件放在空间为W的背包里,每件物品的体积为W1,W2至Wn,与之相对应的价值为P1,P2至Pn。001背包的约束条件是给定几种物品,每种物品有且只有一个,并且有权值和体积两个属性。在01背包问题中,因为每种物品只有一个,对于每个物品只需要考虑选与不选两种情况。如果不选择将其放入背包中,则不需要处理。如果选择将其放入背包中,由于不清楚之前放入的物品占据了多大的空间,需要枚举将这个物品放入背包后可能占据背包空间的所有情况。

二.大多数人对01背包理解不够透彻的原因

关于为什么大多数人对01背包理解不够透彻呢,我们从以下两个角度进行分析:

①01背包问题的代码相对而言比较简短,但是理解起来却没那么容易,这也就导致了很多同志直接萌生了背代码的应对策略,当遇见01背包的问题时直接套代码,感觉这样既省时又省力,但是一旦对01背包的问题进行一小部分的改动,马上就被打回原型了~

②即使理解了01背包的规划策略,但是还是对01背包所创建的数组和数组下标的含义有所模糊,甚至可能当AC了01背包的题目,都不知道01背包的dp数组具体的含义

三.01背包的理论策略(暴力递归)

其实对01背包的分析我们可以将其理解为背包容量和物品的价值和对应的重量的分析:对于每一个物品而言,我们都有将其放入背包和不放入背包两种选择,我们按照物品的顺序从前到后进行策略的选择,无论当前物品是否放入背包,我们都必须要保证一个结果,当我们在处理完最后一个物品的放和不放的决策之后,物品和的总重量必须不大于背包的总容量,至于所说的背包所能容纳的最大价值,我们只需要在每次做完最后一后一个物品的决策之后,选择符合要求的最大价值进行返回即可。而至于在策略途中某个物品的加入使得总物品的重量和超过了背包的总容量,我们直接将这种策略终止即可,而这种理论策略,恰好是背包问题的暴力递归实现的思路,我们选择趁热打铁,直接将关于01背包问题的暴力递归代码(附注释)奉上:
 

public class Bag {
    public static void main(String[] args) {
        int[]w={3,4,5};
        int[]v={6,4,8};
        System.out.println(bestBag(w, v, 10));
    }
    public static int bestBag(int []w,int []v,int weight){
        //检查有效性
        if(w.length==1&&w[0]>weight){
            return 0;
        }
       return process(w,v,0,0,weight);
    }
    //递归过程

    /**
     * 
     * @param w 物品的重量数组
     * @param v 物品的价值数组
     * @param index 遍历的当前物品的序号
     * @param alWeight 当前所遍历过的物品中在选择放与不放之后的重量
     * @param weight 背包的容量
     * @return
     */
    private static int process(int[] w, int[] v, int index, int alWeight, int weight) {
        //判断出口
        if(alWeight>weight){
            return -1;
        }
        if(index==w.length){
            return 0;//成功
        }
        //第一种情况:当前节点上不放值(因为当前物品一旦加入到背包中,背包的总容量就超了)
        int value1=process(w, v, index+1, alWeight, weight);
        //第二种情况:当前结点放入值
        int value2Next=process(w, v, index+1, alWeight+w[index], weight);
        int value2=-1;
        if(value2Next!=-1){
            value2=value2Next+v[index];
        }
        //返回两种结果的最大值
        return Math.max(value1, value2);
    }
}

四.关于01背包实现的动态规划策略

4.1使用二维数组实现01背包的动态规划

关于动态规划问题,我进行实现和分析的策略是将其分为五部分:①递归数组元素的下标以及元素的含义②递推方程的实现③dp数组的初始化④dp数组的遍历顺序⑤在出现错误时,我们通过打印dp数组的方式快速准确的定位问题出现的所在,提高debug的效率

对于01背包问题的分析,我们从这五个方面进行分析:①我们创建二维的dp数组,举例dp数组中的一个元素为dp[i][j],i代表遍历的当前物品,j代表当前dp数组的容量(这样的话我们默认先遍历物品再遍历容量(一层循环是对物品的遍历,二层循环是对容量的遍历),其实反过来遍历也可以,我们后面再进行解释~),dp[i][j]中的具体数值则代表对于当前i个物品的选择中,能够满足j容量的最大价值

②递推公式:我们对01背包的问题进行分析:我们要想实现使得背包容量能取最大值,那么我们一定要使背包的容量尽可能的被占满,且能在当前能够选择的所有物品中进行选择,所以当我们面对每一个物品进行选择时,当要对当前的能选择的全部物品,且尽可能使用所有的背包的剩余容量,结合上面我们所说的dp[i][j]的含义是当前j容量下对当前前i个物品进行选择时,所能容纳下的最大价值,所以dp[i][j]的递推公式应该是由当前物品的选和不选的策略中取其最优解:dp[i][j]=max(dp[i-1][j],dp[i][j-weight[i]]+value[i]),我们对这个递推公式进行解释:dp[i-1][j]的含义我们不选择当前的这个物品,所以我们只对i-1个物品中进行选择,因为不需要考虑当前物品对背包容量的影响,所以对于背包容量的选择,我们选择当前所能选择的最大背包容量j;dp[i][j-weight[i]]+value[i]:这个是我们选择当前的物品放入背包,既然把当前物品放入背包,那么在放入这个物品后,所能容纳的最大容量只剩下【j-weight[i]】了,所以我们在前i-1个物品中在容量为j-weight[i]的情况下选择其最优解,然后将最后物品的价值加入即可。与此同时,我们对前面说到的关于先遍历物品还是先遍历背包容量的问题进行说明:我们先把结论摆在这:都可以,为什么呢?我们观察以下的图片结合递推公式我们可以发现,对于每一个元素,都是由当前元素右边的某个元素和当前元素上面的元素共同推出,所以不论先遍历物品还是先遍历遍历容量,我们都要满足该元素的右边元素和上边元素都被求出来了,但是先遍历容量(相对于按列遍历)还是先遍历物品(按行遍历),其右边和上边的元素都被遍历完成了,所以这两种遍历方式都可以

③初始化:(我们仍然结合上图)对于容量为0的情况下,任凭有再多的物品,我们仍然一个物品都放不下:多以对于第一列的数据,我们将其初始化为0,对于只有物品0的情况下,如果我们当前背包的容量能够大于等于该物品的重量,我们能将物品的价值放入数组,但是如果当前背包的容量小于这个物品的重量,这种背包情况下的数值仍然是0,而其他没有涉及到的行列值呢?我们由递推公式可以得到:当前行列中元素的值与当前这个元素并没有任何关系,而是由其上边和左边元素共同决定的,所以对于这些元素的初始化任何值都可以,我们选择其一,将其初始化为0

④遍历顺序:既然所有的元素都是其上边和左边元素共同推出来的话,所以我们元素的遍历顺序应该选择从上到下,从左到右进行遍历。

⑤打印数组:如果我们发现最后的结果不正确,我们可以从头到尾将数组打印一遍,快速定位错误。

code【代码+注释】如下:
 

/**
 * @author tongchen
 * @create 2023-04-13 15:26
 */
public class DpBag {
    public static void main(String[] args) {
        int[] weight = {1,3,4};
        int[] value = {15,20,30};
        int bagSize = 4;
        MostWeightDpBag(weight, value, 4);

    }

    /**
     * 五部曲:1.dp[i][j] dp[i]是在0-i内存放东西的策略 j是背包容量为j,则dp[i][j]可以理解为在0-i的物品内和容量为j的背包所能存放的最大容量
     * 2.递推方程:dp[i][j]=math.max(dp[i-1][j],dp[i-1][j-w[i]])3.初始化当背包容量为0时,每一个i都是0,当物品为i时,每个背包能存放的最大价值为i
     * @param
     * @return
     */
    public static void MostWeightDpBag(int[]weight,int []value,int maxSize){
        //创建数组
        int[][]dp=new int[weight.length][maxSize+1];
        //初始化
        for(int i=weight[0];i<=maxSize;++i){
            dp[0][i]=value[0];
        }
        //循环
        //一层循环物品
        for(int i=1;i< weight.length;++i){
            //二层循环背包
            for(int j=1;j<=maxSize;++j){
                dp[i][j]=dp[i-1][j];
                if(weight[i]<=j){
                    dp[i][j]=Math.max(dp[i][j],dp[i][j-weight[i]]+value[i]);
                }
            }
        }
        for (int i = 0; i < weight.length; i++) {
            for (int j = 0; j <= maxSize; j++) {
                System.out.print(dp[i][j] + "\t");
            }
            System.out.println("\n");
        }
    }
}

4.2使用一维数组实现01背包的动态规划(滚动数组)

4.2.1从二维数组到一维数组的变化

在讲解真正的从二维数组降解到一维数组之前,我们先看一下从二维数组到一维数组的变化:从二维数组降解到一维数组,显著的变化是我们取消了对物品i的区别,只有j这一个一维数组的下标,代表j容量下所能容纳的最大价值,最本质的原因是将dp[i-1][j]这个元素直接拷贝到了dp[i][j];除此之外我们再来分析其他的变化:①遍历顺序不再什么都可以,而是必须先遍历物品,再遍历容量,对于容量的遍历,我们必须采取容量从大到小的遍历方式②关于初始化:初始化元素的定义因为不再是从上一行的元素进行拷贝,而是在选择(dp[j])和不选择当前物品放入背包(dp[j-weight[i]]+value[i])进行考虑,取其最大值,所以在两者的比较过程中,我们不能使我们的初始化对其产生影响,而dp[j]的含义仍然是在j的容量下,背包所能承受的最大价值,所以初始化不能是负数,也不能对比较产生影响,所以我们将其初始化为0(最小的非负数)

4.2.2对问题重新进行分析以及对二维数组到一维数组变化的难点分析

我们首先进行问题的分析:
我们依然从
①递归数组元素的下标以及元素的含义②递推方程的实现③dp数组的初始化④dp数组的遍历顺序⑤在出现错误时,我们通过打印dp数组的方式快速准确的定位问题出现的所在,提高debug的效率这五部分进行分析:

①dp[j]代表j容量下背包所能容纳的物品的最大价值 ,j代表背包的容量

②由于是将不选择当前物品的情况(将i-1的元素直接落到i),所以我们的递推公式还是从选择和不选择当前物品中选取最大值:dp[j - weight[i]]表示容量为j - weight[i]的背包所背的最大价值。dp[j - weight[i]] + value[i] 表示 容量为 j - 物品i重量 的背包 加上 物品i的价值。(也就是容量为j的背包,放入物品i了之后的价值即:dp[j],此时dp[j]有两个选择,一个是取自己dp[j] 相当于 二维dp数组中的dp[i-1][j],即不放物品i,一个是取dp[j - weight[i]] + value[i],即放物品i,指定是取最大的,毕竟是求最大价值,所以递推公式为:dp[j]=max(dp[j],dp[j-weight[i]]+value[i]);

③初始化:dp[j]表示:容量为j的背包,所背的物品价值可以最大为dp[j],那么dp[0]就应该是0,因为背包容量为0所背的物品的最大价值就是0。那么dp数组除了下标0的位置,初始为0,其他下标应该初始化多少呢?看一下递归公式:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);dp数组在推导的时候一定是取价值最大的数,初始化的值为背包所能承受的最大价值,所以初始化不能是负数,也不能对比较产生影响,所以我们将其初始化为0(最小的非负数)。

④遍历顺序:与二维数组不同的是,一位数组中我们必须先遍历物品,再去遍历容量,且容量必须从后往前遍历(容量从大到小遍历):可能这时候同志们就有疑惑了:为什么不能先遍历容量呢?为什么对容量的遍历不能从前往后呢(容量从大到小),我们依次进行解答:①如果是先遍历容量相当于将数组进行列的遍历,(按照一列一列的顺序进行遍历),我们一定要保证的是当前为j的容量所能容纳的最大值是正确的,那么其选择和不选择当前物品放入背包对应的值也应该是正确的:我们以以下图片的背包进行分析:

如果是按照列进行遍历,其实是违反当前容量所能容纳最大值策略这个原则的:为什么这么说呢?因为如果按照列进行遍历,我们将情景带回到二维数组,相当于是将上一行的元素直接进行了复制,这样也就违反了我们每一个元素其实代表着当前容量下所能容纳的最大价值的物品。

如果大家觉得还是有些抽象的话,大家可以将数组中的元素打印一遍,然后查验一下结果和思路

再讲一下为什么关于容量的遍历为什么要从大到小进行遍历呢?因为如果是从小到大进行遍历,会导致同一个物品重复放置:

倒序遍历是为了保证物品i只被放入一次!但如果一旦正序遍历了,那么物品0就会被重复加入多次!

举一个例子:物品0的重量weight[0] = 1,价值value[0] = 15

如果正序遍历dp[1] = dp[1 - weight[0]] + value[0] = 15dp[2] = dp[2 - weight[0]] + value[0] = 30此时dp[2]就已经是30了,意味着物品0,被放入了两次,所以不能正序遍历;为什么倒序遍历,就可以保证物品只放入一次呢?倒序就是先算dp[2]dp[2] = dp[2 - weight[0]] + value[0] = 15 (dp数组已经都初始化为0)dp[1] = dp[1 - weight[0]] + value[0] = 15所以从后往前循环,每次取得状态不会和之前取得状态重合,这样每种物品就只取一次了。如果大家还是有所疑惑,建议大家将数组打印一遍,去梳理一下整个遍历的过程。

根据这样,我们给出code:


/**
 * @author tongchen
 * @create 2023-04-14 18:00
 */
public class RollingDpBag {
    public static void main(String[] args) {

    }

    /**
     * 1.j代表容量为j2.dp[j]代表j容量下存放的最大值2.dp[j]=max(dp[j],dp[j-weight[i]]+value[i])3.dp[i]=0;4.第一层是物品,从上到下,第二层是容量,从左到右
     */
    public static void dpBag(int[]weight,int []value,int maxSize){
        int[]dp=new int[maxSize+1];
        for(int i=0;i< weight.length;++i){
            for(int j=maxSize;j>=weight[i];--j){
                dp[j]=Math.max(dp[j],dp[j-weight[i]]+value[i]);
            }
        }


    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/431033.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringAMQP的使用

目录一、什么是SpringAMQP二、基本消息队列消息发送消息接收三、WorkQueue队列四、发布订阅模型FanoutExchangeDirectExchangeTopicExchange五、消息转换器一、什么是SpringAMQP 它可以大大的简化我们的开发&#xff0c;不用我们再自己创建连接写一堆代码&#xff0c;具有便捷…

【MySQL--05】表的约束

文章目录 1.表的约束1.1空属性1.2默认值default vs null1.3列描述1.4 zerofill1.5主键primary key1.6 自增长auto_increment1.7唯一键 unique如何设计主键&#xff1f;1.8 外键 foreign key 1.表的约束 真正的约束字段的是数据类型&#xff0c;但是数据类型约束很单一&#xf…

安捷伦E4405B

18320918653 E4405B E4405B|Agilent|ESA-E系列|10G|频谱分析仪|9kHz至13.2GHz 安捷伦 Agilent 惠普 HP 测量速度&#xff1a;28次更新/秒 测量精度&#xff1a;1dB 可选用的10Hz分辨事宽滤波器 机箱可容纳6插槽选件卡 97dB三阶动态范围 能在现场使用的坚固&#xff0c…

mycat2安装配置,mycat2分库分表,mycat2一库多表,mycat2自增id

1、官网下载&#xff08;官网下载地址&#xff09; 官网下载地址 Index of /2.0/ 下载模板 下载jdk包 下载好后吧jdk包房到mycat的lib目录下 2、配置启动 配置结构 mycat配置文件夹 clusters- prototype.cluster.json //无集群的时候自动创建- c0.cluster.json- c1.cluster…

UML与代码的对应关系

五种关系的耦合强弱比较&#xff1a;依赖<关联<聚合<组合<继承 依赖 虚线箭头 可描述为&#xff1a;Uses a 依赖是类的五种关系中耦合最小的一种关系。 因为在生成代码的时候&#xff0c;这两个关系类都不会增加属性。 注意1&#xff1a; Water类的生命期&…

【机器学习】独立成分分析(ICA)及Matlab实现

独立成分分析及Matlab实现1.问题引入2.ICA原理3.ICA算法步骤4.性质与优点5.程序代码6.程序分析7.运行结果1.问题引入 独立成分分析&#xff08;ICA&#xff09;最初由Aapo Hyvrinen等人于1980年代提出&#xff0c;其起源可以追溯到对神经科学和信号处理领域的研究需求。ICA的提…

C语言判断一个日期是在该年的第几天案例讲解

今天是2023年4月11号&#xff0c;我们就用今天举例得出是2023年的第几天。 思路分析 1&#xff09;我们想知道2023年4月11号是2023年的第几天&#xff0c;只需要把1到3月份的天数累加求和然后加上今天日期也就是11就可以算出2023年4月11号是2023年的第几天。 推广&#xff1a;…

kafka集群节点重启后未被topic识别

1.案例 kafka集群的节点重启后,topic为apex的主题识别不到重启后的broker节点id,但是还能识别到副本集还在原来的broker节点上 在kafka manager上查看 继续往下查看 2.查看kafka日志报错原因 以下是两个不同的broker节点报错的报错日志 tail -f /etc/kafka/kafka/logs/ka…

(排序9)非比较排序之计数排序

非比较排序之计数排序 之前讲的七种排序方法的话&#xff0c;都是比较排序&#xff1b;除此之外还有三种非比胶排序&#xff1a;计数排序&#xff0c;基数排序&#xff0c;桶排序。后面两个实际应用没啥&#xff0c;没啥价值。非比较排序的话&#xff0c;他的条件都比较苛刻&a…

HTTP 和 HTTPS(请求响应报文格式 + 请求方法 + 响应状态码 + HTTPS 加密流程 + Cookie 和 Session)

文章目录1. HTTP 是什么2. HTTP 请求报文和响应报文的格式1&#xff09;请求报文格式2&#xff09;响应报文格式3&#xff09;报文中空行的作用3. HTTP 的长连接和短连接4. URL1&#xff09;在浏览器中输入 www.baidu.com 后执行的全部过程5. HTTP 常用的请求方法6. GET 和 POS…

自媒体都在用的5个素材网站,视频、音效、图片全部免费下载~

推荐几个自媒体必备的素材库&#xff0c;免费可商用&#xff0c;建议收藏&#xff01; 1、菜鸟图库 视频素材下载_mp4视频大全 - 菜鸟图库 国内超大的素材库&#xff0c;在这里你可以找到设计、办公、图片、视频、音频等各种素材。视频素材就有上千个&#xff0c;全部都很高清…

后端应用架构

微服务架构划分 ⚠️ 生产环境实际部署中&#xff0c;基础能力、公共基础能力将分别在国内、美国集群部署。在没有引入数据同步的场景下&#xff0c;数据是隔离的。 接入层&#xff08;交互层&#xff09; 接入层主要完成协议转换、熔断限流、统一鉴权等能力&#xff0c;起到保…

Linux网络服务之ftp

目录 一.ftp的相关知识1.1 ftp的简介1.2 ftp的数据连接模式 二.svftpd的安装和配置2.1 svftpd安装3.2 设置本地用户验证访问ftp3.2.1 设置本地用户可以访问ftp&#xff0c;禁止匿名用户登录3.2.2 对本地用户访问切换目录进行限制 3.3 黑名单和白名单的使用3.3.1 黑名单的使用3.…

ASEMI代理AD9959BCPZ原装ADI车规级AD9959BCPZ

编辑&#xff1a;ll ASEMI代理AD9959BCPZ原装ADI车规级AD9959BCPZ 型号&#xff1a;AD9959BCPZ 品牌&#xff1a;ADI /亚德诺 封装&#xff1a;LFCSP-56 批号&#xff1a;2023 安装类型&#xff1a;表面贴装型 引脚数量&#xff1a;56 类型&#xff1a;车规级芯片 工作…

【18图详解3种典型网络拓扑:如何设计一个网络?】

前言 在网络设计的时候&#xff0c;网络架构师需要根据组网的规模设计不同的组网架构&#xff0c;今天介绍3种典型网络架构。 小型组网架构 1、网络拓扑 终端用户接入到交换机&#xff0c;交换机直连防火墙构成的简单网络&#xff0c;防火墙连接internet&#xff0c;对内网的用…

Java并发控制 学习笔记1

一、并发控制的方法 1、悲观锁&#xff1a;常用的互斥锁都属于悲观锁&#xff0c;一个线程访问共享资源时其他线程不能访问。 2、乐观锁&#xff1a;允许同时访问共享数据&#xff0c;只有在提交时利用如版本号检查是否有冲突&#xff0c;应用github。 3、什么时候用乐观锁、什…

开发必备:EsayCode使用以及Oracle自定义模板

前言 写前先问一句&#xff0c;不会还有人在手动写这些基础的sql语句吧&#xff1f;&#xff01; 最近在做Oracle的项目&#xff0c;手写mapper和entity文件真是写到手软&#xff0c;以前MySQL都是找的线上自动生成的&#xff0c;现在也不行了。 找了很长时间&#xff0c;也…

【Python】tkinter的简单使用(Tk对象、三大布局、变量、事件)

本文目录1.tkinter2.Tk对象3.三大布局3.1 pack布局3.2 grid布局3.3 place布局4.变量5.事件1.tkinter tkinter是Tcl/Tk GUI工具包&#xff08;即使用Tcl语言开发Tk图形库&#xff09;的标准Python接口&#xff0c;支持在Windows、macOS、Linux多平台运行。 tkinter是Python自带…

DPDK入门(环境搭建以及小demo)

文章目录零、从0开始配置dpdk环境的虚拟机一、dpdk的编译usertool/dpdk-setup.sh二、dpdk需要什么配置来支持1.多队列网卡2.巨页三、解析接收网络数据的过程经历了什么1.物理网卡2.NIC3.内核协议栈4.标准接口层Posix API5. 应用层上述过程发生的拷贝四、DPDK介绍基于上述接收网…

Github采用Http Push失败

Github采用Http Push失败 Github的密码凭证从2021年起开始就不能用了&#xff0c;现在采用http去push代码时候提示输入的密码要换成令牌&#xff08;token&#xff09;才可以。 如何在Github上生成自己的令牌呢&#xff1f; &#xff08;1&#xff09;简单来说就是将原来输入…