动态规划 --- 01背包

news2024/11/24 7:03:09

动态规划 — 01背包

一直到现在都非常害怕动态规划,因为基本上自己都无法想出dp递推式,太难受了 T.T

今天再一次遇到了需要写01背包的情况,根据自己学习的一点点经历,再稍微总结一下01背包吧,虽然是个被认为dp入门的经典题,但还是希望自己对dp的理解可以更进一步吧。

题目描述

0-1背包的问题提出是,有n个物品,其中物品i的重量是wi,价值为vi,有一容量为C的背包,要求选择若干物品装入背包,使得装入背包的物品总价值达到最大。每个物品只能选择一次。

更加数学化描述:给定 C > 0,wi > 0,vi > 0,1 <= i <= n ,要求找出n元0-1向量(x1,x2,…,xn) xi ∈{0,1},1 <= i <= n ,使得目标函数 在这里插入图片描述

达到最大,并且要满足约束条件。设。

解题

对于每一个物品,我们只有放入背包,或者不放入背包两种可能性,所以我们直接枚举,对,你没看错,直接枚举。

方法一:暴力递归枚举

写递归,需要明确两点,继续递归的条件(变量的变化方向)解递归的条件(变量至何情况不会变)

对于本题,物品是有限的,背包容量是有限的,所以肯定会有两个变量。

//暴力递归
int process1(int index,int bagWeight,int[] weights,int [] values){
    //没东西拿
    if(index >= weights.length){
        return 0;
    }
    //装不下
    if(bagWeight < 0){
        return 0;
    }
    //对于当前物品,只有选择与不选择
    //选择
    int tmp1 = 0;
    //必须选择可以放的下的物品
    if(bagWeight - weights[index] >= 0){
        tmp1 = values[index] + process(index + 1, bagWeight - weights[index], weights, values);
    }
    //不选择
    int tmp2 = process(index + 1, bagWeight, weights, values);

    return Math.max(tmp1, tmp2);
}

优化

从上图我们看到了有很多重复的解,既然如此,何必不存起来呢?所以我们增加一个缓存,计算过的结果我们不再计算

//记忆化递归
int process2(int index,int bagWeight,int[] weights,int [] values, HashMap<Integer,Integer> cache{
    //没东西拿
    if(index >= weights.length){
        return 0;
    }
    //装不下
    if(bagWeight < 0){
        return 0;
    }
    //先走缓存

    //对于当前物品,只有选择与不选择
    //选择
    if(cache.get(index) == null){
        int tmp1 = 0;
        if(bagWeight - weights[index] >= 0){
            tmp1 = values[index] + process(index + 1, bagWeight - weights[index], weights, values);
        }
        //不选择
        int tmp2 = process(index + 1, bagWeight, weights, values);
        int max = Math.max(tmp1, tmp2);
        //将结果放入缓存
        cache.put(index,max);
        return max;
    }else {
        return cache.get(index);
    }
}

方法二:动态规划

暴力递归也就俩变量,我全部给他装起来,也就成了二维DP。

其实到动态规划也非常明了了,最优子结构也就是继续递归的条件,而dp数组,就是对某个背包容量的求解结果得存储。我们来写动态规划。以此为例:weights = {5,4,8,6,9} values = {20,6,8,15,18} bagWeight = 18

物品 0 - i,0 <= i < weights.length

背包容量 0 - j, 0 <= j <= bagWeight

请一定记住 ij 所代表得意义!!!

先画一张表格。

初始化表格。咱们从左往右填

当背包容量 j 为0时,无法装下任何物品,所以最大价值都是0。当只有一个物品的时候,只有 j >= weights[0] 才可以将第一个物品装入背包而且其最大价值就是values[0]

第二行

(这里的背包指 dp[i] [1] 容量为1的背包,dp[i] [2] 容量为2的背包……dp[i] [j] 容量为j的背包)

加入第二个物品,如何寻找最大的价值?当然是找到装入第一个背包时的最大价值啦,所以遍历所有背包。

重量不够怎么能加入进来?所以在 j < weights[i] 时,直接抄上一行的,即继承上一个背包的最大价值数组。

重量够了,那么就需要作出判断:当前物品我到底是要还是不要?我要,则当前 最优价值是 上一个物品在背包容量为 j - weights[i] 时的最大价值再加上当前物品的价值 dp[i-1] [ j - weights[ i ] ] + values[i],我不要,则继承上一个物品在背包容量为 j 时的最大价值的最大价值 dp[i-1] [j]

因此,我们发现了dp 递推式 dp[i ] [ j ] = Math.max(dp[i - 1] [ j ],dp[i - 1] [ j - weights [ i ] ] + values[i]),也就是所谓的最优子结构

看到没?从何处开始变化的?只要当前背包有余量可以某一个添加物品,就会进行比较择优。

第三行

第四行

第五行

//动态规划
int process3(int bagWeight,int[] weights,int [] values){
    int[][] dp = new int[weights.length][bagWeight+1];
    //初始化
    //当只有0号物品的时候
    for(int j = 0; j <= bagWeight; j++){
        dp[0][j] =  j >= weights[0] ? values[0] : 0;
    }
    for(int i = 1; i < weights.length; i++){
        for(int j = 0; j <= bagWeight; j++){
            if(j < weights[i]){
                dp[i][j] = dp[i-1][j];
            }else {
                dp[i][j] = Math.max(dp[i - 1][j],dp[i-1][ j - weights[i]] + values[i]);
            }
        }
    }
    //打印dp数组
    for(int i = 0; i < weights.length; i++){
        System.out.println(Arrays.toString(dp[i]));
    }
    
    return dp[weights.length-1][bagWeight];
}
优化

对于当前背包容量,我们都是从上一行的记录递推下来所以我们可以舍弃计算过的格子,所以尝试采用一维数组记录,将空间复杂度降下至O (n)。观察最终dp表,每一层都是继承了上一层的背包价值并且与加入当前物品进行了择优。

注意:此处需要倒序遍历,为什么?观察我们两种遍历顺序的结果

我们把dp表打印出来看看

顺序

0
[0, 0, 0, 0, 0, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20]
1
[0, 0, 0, 0, 6, 20, 20, 20, 20, 26, 26, 26, 26, 32, 32, 32, 32, 38, 38]
2
[0, 0, 0, 0, 6, 20, 20, 20, 20, 26, 26, 26, 26, 32, 32, 32, 32, 38, 38]
3
[0, 0, 0, 0, 6, 20, 20, 20, 20, 26, 26, 35, 35, 35, 35, 41, 41, 50, 50]
4
[0, 0, 0, 0, 6, 20, 20, 20, 20, 26, 26, 35, 35, 35, 38, 41, 41, 50, 50]

逆序

0
[0, 0, 0, 0, 0, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20]
1
[0, 0, 0, 0, 6, 20, 20, 20, 20, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26]
2
[0, 0, 0, 0, 6, 20, 20, 20, 20, 26, 26, 26, 26, 28, 28, 28, 28, 34, 34]
3
[0, 0, 0, 0, 6, 20, 20, 20, 20, 26, 26, 35, 35, 35, 35, 41, 41, 41, 41]
4
[0, 0, 0, 0, 6, 20, 20, 20, 20, 26, 26, 35, 35, 35, 38, 41, 41, 41, 44]

在第二行进行计算的时候,非常明显物品0被计算了很多次,为什么?观察递推式:dp[j] = Math.max(dp[j] , dp[j - weights[i]] + values[i]),进行递推的时候,必须保证当前背包 j之前的值是上一个背包继承下来的值。所以必须逆序,否则会多次计算物品价值。

//优化
int process4(int bagWeight,int[] weights,int [] values){
    int[] dp = new int[bagWeight+1];
    //初始化
    //当只有0号物品的时候
    for(int j = 0; j <= bagWeight; j++){
        dp[j] = j >= weights[0] ? values[0] : 0;
    }
    for(int i = 1; i < weights.length; i++){
        for(int j = bagWeight; j >= weights[i]; j--){
            dp[j] = Math.max(dp[j],dp[j-weights[i]] + values[i]);
        }
    }
    //打印dp数组
    System.out.println(Arrays.toString(dp));
    return dp[bagWeight];
}

结果验证

public static void main(String[] args) {
        int[] weights = {5,4,8,6,9};
        int[] values = {20,6,8,15,18};
        int bagWeight = 18;
        int res1 = process(0,bagWeight,weights,values);
        System.out.println(res1);
        int res2 = process2(0,bagWeight,weights,values,new HashMap<>());
        System.out.println(res2);
        int res3 = process3(bagWeight,weights,values);
        System.out.println(res3);
        int res4 = process4(bagWeight, weights, values);
        System.out.println(res4);
}
//结果打印
44
44
[0, 0, 0, 0, 0, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20]
[0, 0, 0, 0, 6, 20, 20, 20, 20, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26]
[0, 0, 0, 0, 6, 20, 20, 20, 20, 26, 26, 26, 26, 28, 28, 28, 28, 34, 34]
[0, 0, 0, 0, 6, 20, 20, 20, 20, 26, 26, 35, 35, 35, 35, 41, 41, 41, 41]
[0, 0, 0, 0, 6, 20, 20, 20, 20, 26, 26, 35, 35, 35, 38, 41, 41, 41, 44]
44
[0, 0, 0, 0, 6, 20, 20, 20, 20, 26, 26, 35, 35, 35, 38, 41, 41, 41, 44]
44

总结

关于01背包,为什么要先遍历物品?先遍历背包行不行?为什么第二层循环要逆序?顺序行不行?可以先遍历背包,再遍历物品,遍历可以顺序也可以逆序,这些都可以拿代码进行验证。只不过,遍顺序问题应该对应于题目的含义,这才可以保证直接套用背包模板。本人的理解也大致如此,如果有我有理解错误的地方,还请指出来,大家互相学习。

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

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

相关文章

自学Python必须知道的优秀社区

国内学习Python网站&#xff1a; 知乎学习平台&#xff1a;Python - 基础入门 - 知学堂黑马程序员视频库&#xff1a;大数据学习路线2023版-黑马程序员大数据学习路线图菜鸟教程&#xff1a;菜鸟教程 - 学的不仅是技术&#xff0c;更是梦想&#xff01;极客学院&#xff1a;极…

香港服务器租用攻略:如何优化用户体验?

服务器是网站、应用程序和其他在线内容的核心&#xff0c;对于在线业务来说是至关重要的。如今&#xff0c;随着互联网的普及和数字化转型&#xff0c;越来越多的企业选择在香港租用服务器&#xff0c;以满足其业务需求。但是&#xff0c;租用服务器并不仅仅是选择一个服务商并…

让chatGPT给我写一个CSS,我太蠢了

前言 CSS这东西&#xff0c;让AI写的确有点难度&#xff0c;毕竟它写出来的东西&#xff0c;没办法直接预览&#xff0c;这是其次。重要的是CSS这东西怎么描述&#xff0c;不好描述啊&#xff0c;比如我让他给我制作一个这样的效果出来&#xff0c;没办法描述&#xff0c;所以…

AcWIng1085. 不要62(数位DP)

文章目录 一、问题二、分析三、代码 一、问题 二、分析 这道题涉及的算法是数位DP。如果大家不懂数位DP的话&#xff0c;可以先去看作者之前的文章&#xff1a;第五十章 动态规划——数位DP模型 假设一个数 n n n&#xff0c;我们先求出从 1 1 1到 n n n当中&#xff0c;所有…

《花雕学AI》如何用ChatGPT提升工作效率:适合不同场合的实用技巧大全

实用技巧分类目录 一、最佳ChatGPT 4提示 二、最佳写作和内容创作ChatGPT提示 三、最佳趣味性ChatGPT提示 四、最佳网络开发的ChatGPT提示 五、最佳音乐主题ChatGPT提示 六、最佳职业主题ChatGPT提示 七、最佳用于教育的ChatGPT提示 八、最佳用于市场营销的ChatGPT提示 九、最…

MEET开发者 | 选择和努力一样重要,专访杭州三汇测试工程师齐雪莲

「MEET开发者」栏目的第二期嘉宾是来自杭州三汇的测试工程师——齐雪莲。她是从小在新疆长大的甘肃人&#xff0c;10岁的时候回到了甘肃&#xff0c;大学又考回了新疆&#xff0c;在塔里木大学就读计算机科学与技术专业。 毕业后齐雪莲入职了三汇新疆办事处任测试一职&#xff…

电脑没有网络连接怎么办 电脑无法连接网络怎么解决

这个问题至少困扰我一周 目录 电脑没有网络连接怎么办? 方法一 方法二 方法三 方法四 方法五 方法六 电脑没有网络连接怎么办? 其中也包括了改IP。。电脑就是不好使 #include <iostream> using namespace std; int main(){system("netsh interface ip s…

日志收集系统:将应用产生的数据通过flume收集后发送到Kafka,整理后保存至hbase

目录 前言&#xff1a;功能描述 第一步&#xff1a;flume拉取日志数据&#xff0c;并将源数据保存至Kafka flume配置文件&#xff1a; users&#xff1a; user_friends_raw&#xff1a; events&#xff1a; train&#xff1a; 第二步&#xff1a;Kafka源数据处理 方式一…

滚珠螺杆在设备上的应用

滚珠螺杆跟直线导轨一样&#xff0c;是很多机械设备上不可或缺的重要部件&#xff0c;它是确保机器能够具备高加工精度的前提条件&#xff0c;因此本身对于精度的要求也相当地高。今天&#xff0c;我们就来了解一下滚珠螺杆在不同设备上的应用吧&#xff01; 1、大型的加工中心…

磁盘U盘变本地磁盘寻回教程

磁盘损坏怎么恢复&#xff1f;磁盘是我们工作、学习和生活中常用的信息存储工具&#xff0c;因为容量大、价格便宜而深受人们的喜爱&#xff0c;因此磁盘也成为了我们一些重要信息的信息载具。磁盘U盘变本地磁盘寻回教程这时我们该如何恢复我们丢失的数据呢&#xff1f;这个时候…

ubuntu 安装 notepad++,显示中文菜单,并解决中文乱码问题

1.安装notepad sudo snap install notepad-plus-plus sudo snap install wine-platform-runtime2. notepad中文乱码问题 安装完成之后&#xff0c;输入中文会显示“口口…”&#xff0c;实际上并不是缺少什么windows字库&#xff0c;而是刚安装好的notepad默认字体是Courier …

4月VR大数据:PICO平台应用近400款,领跑国内VR生态

Hello大家好&#xff0c;每月一期的VR内容/硬件大数据统计又和大家见面了。 想了解VR软硬件行情么&#xff1f;关注这里就对了。我们会统计Steam平台的用户及内容等数据&#xff0c;每月初准时为你推送&#xff0c;不要错过喔&#xff01; 本数据报告包含&#xff1a;Steam VR硬…

软件测试面试题最牛汇总,不会有人没有这份文档吧

常见的面试题汇总 1、你做了几年的测试、自动化测试&#xff0c;说一下 selenium 的原理是什么&#xff1f; 我做了五年的测试&#xff0c;1年的自动化测试&#xff1b; selenium 它是用 http 协议来连接 webdriver &#xff0c;客户端可以使用 Java 或者 Python 各种编程语言…

一个.Net版本的ChatGPT SDK

ChatGPT大火&#xff0c;用它来写代码、写表白书、写文章、写对联、写报告、写周边… 啥都会&#xff01; 个人、小公司没有能力开发大模型&#xff0c;但基于开放平台&#xff0c;根据特定的场景开发应用&#xff0c;却是非常火热的。 为了避免重复造轮子&#xff0c;今天给…

你真的会跟 ChatGPT 聊天吗?(上)

前言&#xff1a;即使你对文中提及的技术不大了解&#xff0c;你也可以毫无压力地看完这篇描述如何更好地获得 ChatGPT 生成内容的文章。因为我也是利用 Azure OpenAI 等认知服务来学习&#xff0c;然后就这样写出来的。所以&#xff0c;舒服地坐下来&#xff0c;慢慢看吧&…

网络计算模式复习(三)

云计算和网格技术的差别 相对于网格计算&#xff0c;在表现形式上&#xff0c;云计算拥有明显的特点&#xff1a; 低成本&#xff0c;这是最突出的特点虚拟机的支持&#xff0c;得在网络环境下的一些原来比较难做的事情现在比较容易处理镜像部署的执行&#xff0c;这样就能够…

【微服务 | 学成在线】项目易错重难点分析(媒资管理模块篇·下)

文章目录 视频处理视频编码和文件格式文件格式和视频编码方式区别ProcessBuilder分布式任务调度XXL-JOBXXL-JOB配置XXL-JOB使用分片广播技术方案视频处理方案及实现思路分布式锁 视频处理 视频编码和文件格式 什么是视频编码&#xff1f; 同时我们还要知道我们为什么要对视频…

家用洗地机哪款好?2023入门级智能洗地机

现代社会对卫生日益重视&#xff0c;尤其是在工业、商业和公共场所要求越来越高。传统清洁方式不能满足人们的需求&#xff0c;清洁工作效率低且卫生难以保证。而洗地机的出现&#xff0c;正是为了解决这些问题。它能够深入清洁地面&#xff0c;有效防止不必要的污垢、细菌和病…

小满nestjs(第二十八章 nestjs 事务)

事务的四大特性 事务具有4个基本特征&#xff0c;分别是&#xff1a;原子性&#xff08;Atomicity&#xff09;、一致性&#xff08;Consistency&#xff09;、隔离性&#xff08;Isolation&#xff09;、持久性&#xff08;Duration&#xff09;&#xff0c;简称ACID ① 原子…

2023年5月产品经理认证NPDP线上班火热招生中

产品经理国际资格认证NPDP是新产品开发方面的认证&#xff0c;集理论、方法与实践为一体的全方位的知识体系&#xff0c;为公司组织层级进行规划、决策、执行提供良好的方法体系支撑。 【认证机构】 产品开发与管理协会&#xff08;PDMA&#xff09;成立于1979年&#xff0c;是…