【动态规划】经典问题第三组---背包问题基础

news2024/9/23 23:29:59

前言

小亭子正在努力的学习编程,接下来将开启算法的学习~~

分享的文章都是学习的笔记和感悟,如有不妥之处希望大佬们批评指正~~

同时如果本文对你有帮助的话,烦请收藏点赞关注支持一波, 感激不尽~~

刷题专栏在这里~~

简单介绍一下什么是背包问题:

有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。

每一件物品其实只有两个状态:取或者不取

所以可以使用回溯法搜索出所有的情况,那么时间复杂度就是o(2^n),这里的n表示物品数量。

但是这里我们将详细介绍用动态规划的思想去解题。

举个栗子:
背包最大重量为4。
物品为:
重量 | 价值
物品0 1 15
物品1 3 20
物品2 4 30
问背包能背的物品最大价值是多少?

以下讲解和图示中出现的数字都是以这个例子为例。

二维dp数组01背包

动归五部曲分析:

1.确定dp数组以及下标的含义

 

 对于背包问题, 可以使用二维数组,即dp[i][j] ,表示从下标为 0-i 的物品里任意取,放进容量为 j 的背包,价值总和最大是多少

2.确定递推公式

由上图和dp数组的定义可以看出,有两个方向推出来dp[i][j]

  • 不放物品i:就是当物品 i 的重量大于背包 j 的重量时,物品 i 无法放进背包中,所以被背包内的价值依然和前面相同,此时dp[i][j]就是dp[i - 1][j]。推出公式:dp[i - 1][j]
  • 放物品i:当背包能放下物品 i 的重量时,dp[i - 1][j - weight[i]]  为背包容量为 j - weight[i]的时候不放物品i的最大价值,那么dp[i - 1][j - weight[i]] + value[i] (物品i的价值),就是背包放物品i得到的最大价值,推出公式dp[i - 1][j - weight[i]] + value[i]
  • 最后,dp[i][j] 需要在上述两个方向上取出最大值

所以递推公式: dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);

3.dp数组如何初始化

首先从dp[i][j]的定义出发,如果背包容量j为0的话,即dp[i][0],无论是选取哪些物品,背包价值总和一定为0。

然后,由递推公式: dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]) ;

可以看出 i 是由 i-1 推导出来,那么i为0的时候就一定要初始化。

dp[0][j],即:i为0,存放编号0的物品的时候,各个容量的背包所能存放的最大价值。

那么很明显当 j < weight[0]的时候,dp[0][j] 应该是 0,因为背包容量比编号0的物品重量还小。

当j >= weight[0]时,dp[0][j] 应该是value[0],因为背包容量放足够放编号0物品。

 

dp[0][j] 和 dp[i][0] 都已经初始化了,那么其他下标应该初始化多少呢?

其实从递归公式: dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]); 可以看出dp[i][j] 是由左上方数值推导出来了,那么 其他下标初始为什么数值都可以,因为都会被覆盖。

初始-1,初始-2,初始100,都可以!

但只不过一开始就统一把dp数组统一初始为0,更方便一些,所以我们把其他位置初始化为0.

 结果如图:

 

4.确定遍历顺序

观察上图和递推公式,dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);

可以看出 :dp[i][j] 是靠 dp[i-1][j] 和 dp[i - 1][j - weight[i]] 推导出来的。

dp[i-1][j]和dp[i - 1][j - weight[i]] 都在dp[i][j]的左上角方向(包括正上方向),那么先遍历物品,或者先遍历重量都可以。

(我们这里先遍历物品)

5.举例推导dp数组

这一步的作用主要是为了验证一下前面的步骤得出的结果是否符合预期,我个人觉得在前面找公式的时候如果看不出规律也可以先多写几组结果,在观察。 

java代码实现:

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

   
    public static void testWeightBagProblem(int[] weight, int[] value, int bagSize){

        // 创建dp数组
        int goods = weight.length;  // 获取物品的数量
        int[][] dp = new int[goods][bagSize + 1];  //这个+1是为了让数组下标和我们需要的下标对齐

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

        // 填充dp数组,因为物品和重量的数组下标是从0,开始的,所以这里从下标1开始遍历
        for (int i = 1; i < weight.length; i++) {
            for (int j = 1; j <= bagSize; j++) {
                if (j < weight[i]) {
                    /**
                     *放不下物品 i 的情况
                     */
                    dp[i][j] = dp[i-1][j];
                } else {
                    /**
                     * 能放下物品 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");
        }
    }
}

一维dp数组(滚动数组)

简单介绍下什么是滚动数组:

滚动数组的由来,需要满足的条件是上一层可以重复利用,直接拷贝到当前层。

对于背包问题其实状态都是可以压缩的。

在使用二维数组的时候,递推公式:dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);

其实可以发现如果把dp[i - 1][j]那一层拷贝到dp[j]上,表达式完全可以是:

dp[j] = max(dp[j], dp[j - weight[i]] + value[i] )

dp[j] 的含义就是:容量为j的背包,所背的物品价值可以最大为dp[j]。

动归五部曲分析:

1.确定dp数组的定义

在一维dp数组中,dp[j]表示:容量为j的背包,所背的物品价值可以最大为dp[j]。

2.一维dp数组的递推公式

dp[j]可以通过 dp[j - weight[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] )

3.一维dp数组如何初始化

dp[j]表示:容量为j的背包,所背的物品价值可以最大为dp[j],那么dp[0]就应该是0,因为背包容量为0所背的物品的最大价值就是0。

dp数组在推导的时候一定是取价值最大的数,如果题目给的价值都是正整数那么非0下标都初始化为0就可以了。

这样才能让dp数组在递归公式的过程中取的最大的价值,而不是被初始值覆盖了

那么我假设物品价值都是大于0的,所以dp数组初始化的时候,都初始为0就可以了。

4.一维dp数组遍历顺序

【这一块和二维数组不一样,需要格外注意】

使用滚动数组时,我们在遍历背包的时候需要倒序遍历

这是为什么呢?

举个栗子:

物品0的重量weight[0] = 1,价值value[0] = 15,已经初始化了(dp数组已经都初始化为0)

 如果正序遍历

dp[1] = dp[1 - weight[0]] + value[0] = 15

dp[2] = dp[2 - weight[0]] + value[0] = 30

此时dp[2]就已经是30了,意味着物品0,被放入了两次,不符合题目要求

倒序就是先算dp[2]

dp[2] = dp[2 - weight[0]] + value[0] = 15

dp[1] = dp[1 - weight[0]] + value[0] = 15  (注:dp[0] = 0.)

所以从后往前循环,每次取得状态不会和之前取得状态重合,这样每种物品就只取一次了。

再来看看两个嵌套for循环的顺序,代码中是先遍历物品嵌套遍历背包容量,那可不可以先遍历背包容量嵌套遍历物品呢?

答案是:不可以!

因为一维dp的写法,背包容量一定是要倒序遍历(原因上面已经讲了),如果遍历背包容量放在上一层,那么每个dp[j]就只会放入一个物品,即:背包里只放入了一个物品。

二维数组为什么没有限制?

因为滚动数组需要重复利用(拷贝)上一层的数据

而,对于二维dp,dp[i][j]都是通过上一层即dp[i - 1][j]计算而来,本层的dp[i][j]并不会被覆盖

【如果还是不理解的话可以写代调试码跑一下观察试试】

5.举例推导dp数组

一维dp,分别用物品0,物品1,物品2 来遍历背包,最终得到结果如下:

 java 代码实现:

    public static void main(String[] args) {
        int[] weight = {1, 3, 4};
        int[] value = {15, 20, 30};
        int bagWight = 4;
        testWeightBagProblem(weight, value, bagWight);
    }

    public static void testWeightBagProblem(int[] weight, int[] value, int bagWeight){
        int wLen = weight.length;
        //定义dp数组:dp[j]表示背包容量为j时,能获得的最大价值
        int[] dp = new int[bagWeight + 1];
        //遍历顺序:先遍历物品,再遍历背包容量
        for (int i = 0; i < wLen; i++){
            for (int j = bagWeight; j >= weight[i]; j--){
                dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
            }
        }
        //打印dp数组
        for (int j = 0; j <= bagWeight; j++){
            System.out.print(dp[j] + " ");
        }
    }

【本文就到这里了,后面还会持续更新动态规划相关内容,一键三连,一起刷题呀~~】

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

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

相关文章

再学C语言50:C库中的字符串函数(2)

一、strcmp()函数 功能&#xff1a;对字符串内容进行比较&#xff0c;如果两个字符串参数相同&#xff0c;函数返回0 示例代码&#xff1a; /* test of strcmp() function */ #include <stdio.h> #include <string.h>#define NAME "Forster"int main(…

rem实现移动端自适应

rem实现自适应的原理&#xff1a;就是屏幕的宽度/任意数&#xff08;推荐设计稿除下来是整数&#xff0c;方便计算&#xff09;&#xff0c;接着设置根html的font-size为这个数&#xff0c;比如设计师给我们的设计稿宽度为750px&#xff0c;我们可以用750/7.5得到100再赋值给ht…

rnn、lstm、cnn、transformer

rnn不能并行的原因&#xff1a;不同时间步的隐藏层之间有关联。 rnn中batch的含义 如何理解RNN中的Batch_size&#xff1f;_batch rnn_Forizon的博客-CSDN博客 rnn解决的问题 不定长输入带有顺序的序列输入1 rnn前向传播 2 rnn中的反向传播 还有loss对其他参数的求导&#…

Flutter渲染原理

一 Widget Element RenderObject 之间的关系 1 Widget 在Flutter 中&#xff0c;万物皆是Widget,无论是可见的还是功能型的。一切都是Widget. 官方文档中说的Widget 使用配置和状态来描述View 界面应该长什么样子。 它不仅可以表示UI元素&#xff0c;也可以表示一些功能性的…

前端学习:HTML JavaScript

目录 一、JavaScript 使HTML页面更具有动态性和交互性 浏览器中的 JavaScript 能做什么&#xff1f; 二、 HTML三、HTML标签 ​编辑四、JavaScript 的功能示例 1. JavaScript 能够更改内容&#xff1a; 2. JavaScript能够更改样式&#xff1a;3.JavaScript能够更改属性 五、…

拼多多运营中需要采集淘宝天猫京东平台商品详情页面数据上架拼多多店铺,如何使用技术封装接口实现

业务背景&#xff1a;电商平台趋势&#xff0c;平台化。大家可以看到大的电商都开始有自己的平台&#xff0c;其实这个道理很清楚&#xff0c;就是因为这是充分利用自己的流量、自己的商品和服务大效益化的一个过程&#xff0c;因为有平台&#xff0c;可以利用全社会的资源弥补…

FT2000+ openEuler 20.03 virsh创建qemu kvm虚拟机 启动qemu kvm

安装qemu、libvirt yum install libvirt libvirt-client -y yum install qemu -y 安装固件包 yum install edk2-aarch64 固件文件 配置/etc/libvirt/libvirtd.conf auth_tcp "sasl" listen_tcp 1 listen_tls 0 tcp_port "16509" unix_sock_dir …

RK3588_X703 音频调试笔记

x703项目扩接板有接喇叭音频&#xff0c;硬件如下&#xff1a; 喇叭SPK播放无声的时候&#xff0c;首先要测R43贴片电压正常。 需要dts中正确配置SPK_CTL_H的GPIO脚&#xff1a; es8316_sound: es8316-sound {status "okay";compatible "rockchip,multicodec…

javaScript---js如何实现继承

目录 1、构造函数继承 2、原型链继承 3、组合继承 4、class继承 5、寄生组合继承 JavaScript 是以对象为基础&#xff0c;以函数为模型&#xff0c;以原型为继承的面向对象开发模式。 javascript继承的作用&#xff1a; 可以不调用“父类”的构造方法就创造新的实例&…

JavaScript 基础入门速成上篇

JavaScript 嵌入页面的方式 1. 行间事件 <button onclick"alert(点击按钮)">按钮</button> 2. script标签 <script type"text/javascript">console.log(Hello javascript !) </script> 3. 外部引入 <script type"t…

并发编程三要素:可见性、原子性、有序性

一、介绍 1、什么是可见性、原子性、有序性&#xff1f; 可见性&#xff08;visibility&#xff09;&#xff1a;指一个线程对共享变量的修改能够被其他线程立即看到的特性。在多线程环境下&#xff0c;如果一个线程修改了一个共享变量的值&#xff0c;那么其他线程可能无法立…

时隔两个多月,一起来看ChatGPT现况如何?

ChatGPT这股风吹了两个多月&#xff0c;时至今日&#xff0c;各平台上与ChatGPT相关的文章&#xff0c;到现在依旧拥有着不小的流量。三月中旬上线了ChatGPT-4&#xff0c;与我们的文心一言前后脚发布&#xff0c;而后阿里的“通义千问”也展现了不俗的实力&#xff0c;那到现在…

【多线程】初识多线程

1. 为什么要学习多线程&#xff1f;首先相信各位小伙伴在学习 JavaSE 的时候&#xff0c;肯定写过一些小游戏吧&#xff0c;比如猜数字&#xff0c;关机小程序...但是如果现在要在猜数字小游戏上面加上一个功能&#xff0c;设定20秒没猜中&#xff0c;就判定游戏失败&#xff0…

数据结构:什么是堆,和二叉树有什么关系

堆栈模型 JS 代码执行时&#xff0c;值类型变量存储在栈&#xff0c;引用类型变量存储在堆。 // 变量 a 存储在栈里 let num1 1 let num2 num1 num2 2 // 这时打印 num1 是 1&#xff0c;num2 是 2。// { a: 1 } 存在堆里&#xff0c;obj1 只是一个指针引用 let obj1 { a…

华为 WATCH Ultimate 如何开通和使用北斗卫星卡

华为春季新品发布会发布了全新系列手表华为WATCH Ultimate非凡大师&#xff0c;实现了“向上捅破天”的突破性应用&#xff0c;让大家通过手表与世界一直相连。当用户在周围无信号&#xff08;无蜂窝网络/WLAN网络覆盖&#xff09;的情况下&#xff0c;处于空旷无遮挡的环境时&…

基于超声波传感器的液位测量及控制系统设计(STM32)

一、引言 随着工业的发展&#xff0c;计算机、微电子、传感器等高新技术的应用和研究&#xff0c;液位仪表的研制得到了长足的发展&#xff0c;以适应越来越高的应用要求。液位的测量在工业生产过程中已经起着相当重要的作用&#xff0c;其类型大概可以分为接触型和非接触型两大…

最近搭了一个数据监测看板。

在大数据时代&#xff08;这个开头我已经看腻了 &#xff09;&#xff0c;为了挖掘冗余数据的价值&#xff0c;数据分析需求日益增多&#xff0c;而分析结果的常见表现形式有数据分析报告和数据看板&#xff08;大屏&#xff09;&#xff0c;与报告文档不同&#xff0c;数据看板…

8.1.0:DHTMLX Suite JavaScript UI Crack

适用于现代 Web 应用程序的强大 JavaScript 小部件库 - DHTMLX 套件 用于创建现代用户界面的轻量级、快速且通用的 JavaScript/HTML5 UI 小部件库。 DHTMLX Suite 有助于推进 Web 开发和构建具有丰富功能的数据密集型应用程序。 DHTMLX Suite 是一个 UI 小部件库&#xff0c;用…

移动端高性能Unity播放器实现方案

前情提要&#xff1a; 视听体验再进化——如何在24小时内全面升级你的视频应用 如何打造新时代的终端播放产品&#xff1f; 随着VR、AR、元宇宙等新玩法的出现&#xff0c;Unity平台的视频播放需求逐渐增加&#xff0c;比如下面两个动图就是在百度真实的案例。前者是演唱会场景…

ChatGPT能够知道当下最流行的开发语言,以及各语言哪个开发框架最火吗?

如果你准备成为一名开发人员&#xff0c;但是面对琳琅满目的开发语言&#xff0c;然后每种语言的开发框架却无从下手&#xff0c;张三推荐你学这个&#xff0c;李四推荐你学那个&#xff0c;而你的时间又是有限的&#xff0c;于是我决定问一问这个万事通ChatGPT。 目录 1. 目前…