从二维数组到一维数组——探索01背包问题的动态规划优化

news2024/12/23 19:06:50

文章目录

  • 题目
  • 前知
    • 背包问题
  • 二维dp数组
    • 一、思路
    • 二、解题方法
    • 三、Code
  • 一维dp数组
    • 一、思路
    • 二、解题方法
    • 三、Code
  • 总结


本文将继续上一篇博客爬楼梯之后继续讲解同样用到了动态规划的 01背包问题

在解决动态规划问题时,我们经常面临着空间复杂度的挑战。01背包问题是一个典型的例子,通常使用二维数组来表示状态转移,但这样会带来额外的空间开销。在本文中,我们将探讨如何通过优化空间复杂度,将01背包问题从二维数组降维到一维数组,以提高算法的效率和性能。

题目

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

01背包重量价值
物品0115
物品1320
物品2430

前知

背包问题

01背包问题是一个经典的动态规划问题,通常用来解决如何在有限的背包容量下选择物品以获得最大价值的问题。问题的描述是:给定一组物品,每种物品都有自己的重量和价值,在限定的背包容量下,选择哪些物品放入背包可以使得背包内物品的总价值最大化,且不能超过背包的承重。背包问题有以下几种:
在这里插入图片描述

在这里插入图片描述


二维dp数组

一、思路

首先,我们使用动态规划来解决01背包问题。通常,我们会使用一个二维数组dp[i][j]来表示在前i个物品中,背包容量为j时的最大总价值。我们初始化dp数组为0,并通过状态转移方程来更新dp数组,最终得到dp[n][C]作为问题的解,其中n为物品个数,C为背包容量。

01背包重量价值
物品0115
物品1320
物品2430

二、解题方法

动规五部曲

  1. 确定dp数组及下标i的含义:从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是dp[i][j] ,如下图所示:
    在这里插入图片描述

  2. 确定递推公式:dp[i][j]可以由两个方向推出,要么是放i,要么就不放i,递推公式为: dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);

  • 不放物品i时:里面的最大价值实际上是dp[i - 1][j],因为此时代表已经达到背包最大容量时的最大价值,和上一层状态相同,当物品i的重量大于背包j的重量时,物品i无法放进背包中,所以背包内的价值依然和前面相同。
  • 放物品i时:现在里面已经放了i,最大价值所以肯定有 value[i])dp[i - 1][j - weight[i]]代表的是放物品i并且剩余空间能够刚好放下物品i时的最大价值
  1. dp数组如何初始化:如果背包容量j为0的话,即dp[i][0],无论是选取哪些物品,背包里面都放不下物品,背包价值总和一定为0,如图:在这里插入图片描述
    再由递推公式可以看出dp[i][j]一定是由dp[i-1]得出的,所以还需要初始化最上面一层的dp数组,也就是dp[0][j]:存放编号0的物品的时候,各个容量的背包所能存放的最大价值。当最小的背包容量j连一个物品都存不下的时候,那么就让dp[0][j]=0,例如题目中把物品0的重量改为2,dp[0][1]就为0。如果背包能放得下物品的时候,dp[0][j] 应该是value[0],并且都是所有容量的都为value[0],因为01背包问题,物品只能取一次,如图:
    在这里插入图片描述
for (int j = 0 ; j < weight[0]; j++) {  // 当然这一步,如果把dp数组预先初始化为0了,这一步就可以省略,但很多同学应该没有想清楚这一点。
    dp[0][j] = 0;
}
// 正序遍历
for (int j = weight[0]; j <= bagweight; j++) {
    dp[0][j] = value[0];
}

其它非0下标的dp数组都可以初始化为0,因为都是由上面一层或左上方推出来的,0会被覆盖掉
在这里插入图片描述

  1. 确定遍历顺序:从递推公式 dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);可以看出,有两个for循环遍历方向得到dp数组,一个是物品,另一个是背包容量,由于dp[i][j]所需要的数据就是左上角,所以先遍历物品还是先遍历背包容量都没什么太大问题

先遍历物品,再遍历背包容量,一行一行遍历:
在这里插入图片描述
先遍历背包容量,再遍历物品,一列一列遍历:
在这里插入图片描述

  1. 举例推导dp数组:最终结果dp[2][4]
    在这里插入图片描述

三、Code

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);
    }

    /**
     * 动态规划获得结果
     * @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");
        }
    }
}

一维dp数组

一、思路

使用二维数组会带来额外的空间开销。为了优化空间复杂度,我们可以通过滚动数组将二维数组降维为一维数组。我们定义一个一维数组dp[j],其中dp[j]表示背包容量为j时的最大总价值。然后,我们先从前往后遍历物品,再通过逆序遍历背包容量dp数组来更新状态,最终得到dp[C]作为问题的解。这样,我们成功将空间复杂度从O(n*C)降低到了O(C),大大提高了算法的效率和性能。

01背包重量价值
物品0115
物品1320
物品2430

二、解题方法

把dp[i - 1]那一层拷贝到dp[i]上,对状态进行压缩,只用一个dp[j]滚动数组

动规五部曲

  1. 确定dp数组及下标i的含义:容量为j的背包,所背的物品价值可以最大为dp[j]

  2. 确定递推公式:一维dp数组相对于二维dp数组的写法,就是把dp[i][j]中i的维度去掉了。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数组如何初始化:当背包容量为0时,所背的物品价值dp[0]最大就是0,dp数组非0下标都让值为1,这样在递推之后,取到的最大值就会是递推过程中的最大值,而不会被其它初始值给覆盖掉

  4. 确定遍历顺序:根据递推公式得出同样有两个方向,首先从前到后遍历物品,再倒序遍历背包容量,这个顺序不能像二维dp数组一样调换顺序。

  • 如果是先倒序遍历背包容量,再正序遍历物品的话,那么里面的for循环会重复从第一个物品开始遍历,就只会在背包里添加一个物品。
  • 如果是先从前到后遍历物品,再正序遍历背包容量的话,物品0就不止添加了一次,而会被重复添加,从后向前遍历的话,前面的状态就不会被后面所调用到。
    正序:dp[1] = dp[1 - weight[0]] + value[0] = 15
    dp[2] = dp[2 - weight[0]] + value[0] = 30
    倒序:dp[2] = dp[2 - weight[0]] + value[0] = 15 (dp数组已经都初始化为0)
    dp[1] = dp[1 - weight[0]] + value[0] = 15
  • 二维dp数组遍历的时候不用倒序是因为对于二维dp,dp[i][j]都是通过上一层即dp[i - 1][j]计算而来,本层的dp[i][j]并不会被覆盖
for(int i = 0; i < weight.size(); i++) { // 遍历物品
    for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量
        dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);

    }
}
  1. 举例推导dp数组:用题目进行举例
    在这里插入图片描述

三、Code

    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] + " ");
        }
    }

总结

通过这篇博客,读者可以清晰地了解如何通过优化空间复杂度,将01背包问题的动态规划解法从二维数组降维到一维数组,并且可以对比二者在性能上的差异,从而更好地掌握这一知识点。希望本文能够帮助读者更好地理解和应用动态规划算法在01背包问题中的使用,如果有任何疑问或者建议,欢迎留言讨论🌹

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

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

相关文章

前端三剑客 —— JavaScript (第二节)

目录 内容回顾 数据类型 基本数据类型&#xff1a; 引用数据类型&#xff1a; 常见运算 算术运算符 比较运算符 逻辑运算符 赋值运算符 自增/减运算符 三目运算符 位运算符 内容回顾 1.概述 2.基本数据 1.使用方式&#xff08;行内、页面、外部&#xff09; 2.对话框…

通信安全之数据加密

数据安全的需求如今越来越重要&#xff0c;本篇简单举例给日常的TCP/UDP通信加密&#xff0c;至少能让想干坏事的崽犯罪的成本更高一些&#xff08;如果会一些BPF的&#xff0c;可能难不住这些崽&#xff09;&#xff0c;能让我们的数据更安全一点。 经典TCP socket编程 下面…

佑雅的小布谷数据平台获取token如何实现

小博股数据开放平台是面向全部用户的股票数据开放平台&#xff0c;通过调用接口可以获取股票的历史数据。在调用之前需要进行下面的准备工作&#xff0c;第一步注册&#xff1a; 用户在注册之后&#xff0c;登录点击头像进入个人中心&#xff0c;在功能模块的最下方有一个创建应…

IntelliJ IDEA 2024.1安装与激活[破解]

一&#xff1a;IDEA官方下载 ①如题&#xff0c;先到IDEA官方下载&#xff0c;简简单单 ②IDEA官方&#xff1a;IntelliJ IDEA – the Leading Java and Kotlin IDE 二&#xff1a;获取脚本 &#x1f31f;网盘下载&#xff1a;jetbra (密码&#xff1a;lzh7) &#x1f31f;获取…

51单片机入门_江协科技_25~26_OB记录的笔记_蜂鸣器教程

25. 蜂鸣器 25.1. 蜂鸣器介绍 •蜂鸣器是一种将电信号转换为声音信号的器件&#xff0c;常用来产生设备的按键音、报警音等提示信号 •蜂鸣器按驱动方式可分为有源蜂鸣器和无源蜂鸣器&#xff08;开发板上用的无源蜂鸣器&#xff09; •有源蜂鸣器&#xff1a;内部自带振荡源&a…

报文 消息

报文消息域 MsgField name 域名称 length 长度 fillChar 填充字符 fillSide 填充位置 报文消息片 MsgPiece 由多个消息域按一定的顺序组成 private List<MsgField> itemList new LinkedList<~>();组装消息 报文消息包 MsgPackage 由多个消息片组成 String[]…

FreeRTOS启动任务调度器

FreeRTOS启动任务调度器 这部分内容就要去深入了解源码以及熟悉汇编语言的操作。依旧正点原子的视频。下面首先看开启任务调度器这部分源码&#xff1a; 1开启任务调度器 任务调度器用于启动任务调度器&#xff0c;任务调度器启动后&#xff0c; FreeRTOS 便会开始进行任务调…

MyBatis 应用的组成

王有志&#xff0c;一个分享硬核Java技术的互金摸鱼侠 加入Java人的提桶跑路群&#xff1a;共同富裕的Java人 大家好&#xff0c;我是王有志。在上一篇文章的最后&#xff0c;我们写了一个简单的例子&#xff0c;今天我们就通过这个例子来看一看一个标准的 MyBatis 应用程序由哪…

PS入门|如何使用“主体”功能进行抠图?

前言 前段时间讲到给各种图标和LOGO抠图的办法&#xff0c;分别使用的是 钢笔工具蒙版 PS入门&#xff5c;规规矩矩的图形怎么抠出来&#xff1f; 魔棒工具蒙版 PS入门&#xff5c;黑白色的图标怎么抠成透明背景 色阶蒙版 PS入门&#xff5c;目标比较复杂&#xff0c;但背景…

HTML+CSS+JS复习回顾

环境搭建 下载VScode&#xff0c;依次下载插件&#xff1a;HTML CSS support、Live Server、Auto Rename Tag 一、HTML篇 HTML通过一系列的标签&#xff08;元素&#xff09;来定义文本、图像、链接等。HTML标签是由尖括号包围的关键字。标签通常成对出现&#xff0c;包括开…

在Spring中使用Redis

端口怎么设置&#xff0c;看我前一篇文章 前面使用jedis&#xff0c;通过Jedis对象中各种方法来操作redis的。 此处Spring中则是通过StringRedisTemplate来操作redis。 最原始提供的类是RedisTemplate StringRedisTemplate是RedisTemplate的子类&#xff0c;专门处理文本数据的…

2014最新AIGC创作系统ChatGPT网站源码+AI绘画网站源码+GPT4-All联网搜索模型

一、文章前言 SparkAi创作系统是基于ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统&#xff0c;支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;那么如何搭建部署AI创作ChatGPT&#xff1f;小编这里写一个详细图文教程吧。已支持…

无重复的最长字串

&#x1f4dd;个人主页&#xff1a;五敷有你 &#x1f525;系列专栏&#xff1a;算法分析与设计 ⛺️稳中求进&#xff0c;晒太阳 问题 给定一个字符串&#xff0c;我们需要找到该字符串中的最长无重复子串的长度。 示例 让我们以一个具体的示例来说明这个问题&#…

四、书城开发--3、书城图书部分的开发

书城图书部分 首先我们做书城首页搜索栏下面的图片展示 我们在书城首页组件中通过home请求方法中获取回来的数据中&#xff0c;打印出来可以看到那个banner就是我们现在要的图片 我们在data中定义一个变量banner用来存放获取回来的数据中的banner 然后把它展示出来就可以了&a…

B. Burning Midnight Oil Codeforces Round 112 (Div. 2)

题目链接&#xff1a; Problem - 165B - Codeforceshttps://codeforces.com/problemset/problem/165/B 题目大意&#xff1a; 最后写了至少n个&#xff0c;每次衰减k倍&#xff08;/k&#xff09;&#xff0c;问最初的v最小为多少。 思路&方法&#xff1a; 二分答案。 AC代…

想要品牌传播有效,先清楚这三个本质问题

在互联网时代&#xff0c;企业想要提高市场竞争力就需要做好品牌传播。然而有许多企业在做品牌传播时都会踩坑&#xff0c;原因是因为忽视了这三点&#xff0c;接下来就让媒介盒子和大家分享&#xff1a; 一、 文案本质是“购买理由” 在文案技巧中经常会出现一些词&#xff…

重学Java,JDK安装,Java环境配置,Could not find Java SE Runtime Environment问题解决

文章目录 前言JDK下载什么是JDK下载JDK官网下载历史版本下载 JDK安装生成JRE配置环境变量进入环境变量配置界面新建系统变量JAVA_HOME编辑系统变量PATHPath编辑界面1Path编辑界面2 配置CLASSPATH 验证安装情况问题反馈Error: opening registry key Software\JavaSoft\Java Runt…

WordPress网站备份和迁移教程

我们之前遇到购买了hostease的客户需要进行wordpress的网站备份的迁移操作。 以下是一份完整的指南&#xff0c;介绍了备份和迁移WordPress网站的步骤&#xff1a; 步骤一&#xff1a;备份WordPress网站 使用插件进行备份&#xff1a; 安装并激活备份插件&#xff0c;例如Up…

SSH远程登陆系统(RedHat9)

ssh的基本用法 ssh hostname/IP # 如果没有指定用什么用户进行连接&#xff0c;默认使用当前用户登录 ssh –l username hostname/IP ssh usernamehostname ssh usernameIP在第一次连接到服务器时&#xff0c;会自动记录服务器的公钥指纹信息 如果出现密钥变更导致错误可以…

Spring Cloud 集成 Redis 发布订阅

目录 前言步骤引入相关maven依赖添加相关配置 使用方法发布订阅发布一个消息 注意总结 前言 在当今的软件开发领域&#xff0c;分布式系统已经成为一种主流的架构模式&#xff0c;尤其是在处理大规模、高并发、高可用的业务场景时。然而&#xff0c;随着系统复杂性的增加&…