代码随想录day35--动态规划的应用2||01背包理论基础、携带研究材料

news2024/11/15 23:43:38

01背包理论基础

有n件物品和一个最多能背重量为w的背包。第i件物品的重量是weight[i],得到的价值为

value[i]。每件物品只能用一次,将这些物品装入背包里物品价值总和最大。

这是很标准的背包问题,很多同学看到后很自然的就想到了背包,我们要知道为什么用背包问题,也就是动态规划求解,如果使用回溯算法进行求解,搜索出所有的情况,那么时间复杂度就是:

O(n^2),这里的n表示物品数量。

所以暴力的解法是指数级别的时间复杂度,进而需要动态规划的解法来进行优化

下面,举一个经典的背包问题的例子:

背包最大重量为4.

物品为:

重量价值
物品0115
物品1320
物品2430

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

首先使用二维dp数组解决

依旧还是动态规划五部曲

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

dp[i][j]表示下标为[0-i]的物品中任意取,放进容量为j的背包,价值总和最大是多少

可以结合图表来分析二维数组的定义

dp数组的定义,一定要明确,因为之后的步骤都在围绕dp数组的含义进行分析和操作

如果有些想不通,或者懵逼,那就回顾一下i代表什么,j又代表什么

2.确定递推公式

根据dp[i][j]的含义,可以推导出,可以由两个方向推导出dp[i][j]

·不放物品:由dp[i-1]推出,即背包容量为j,里面不放物品i的最大价值,此时dp[i][j]就是

dp[i-1][j]。(其实就是当物品i的重量大于背包j的重量时,物品i无法放进背包中,所以背包内的价值依旧和前面相同)

·放物品:由dp[i-1][j-weight[i]]推出,dp[i-1][j-weight[i]]为背包容量为j-weight[i]的时候不放物品i的最大价值,那么dp[i-1][j-weight[i]]+value[i],就是背包放物品i得到的最大价值

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

3.dp数组如何初始化

关于初始化,一定要和dp数组的定义吻合,否则到递推公式的时候就会越来越乱

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

从状态转移方程中可以看出,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物品

所以初始化代码如下:

for (int i = 0 ; i < weight[0]; i++) {  
    dp[i][0] = 0;
}
for (int j = weight[0]; j <= bagweight; j++) {
    dp[0][j] = value[0];
}

dp[i][0]和dp[0][j]都已经初始化完毕,然后就开始其他下标的初始化,其实已经不用再继续初始化了,因为从递推公式来看,已经可以知道,无论初始化任何值,都会被覆盖

4.确定遍历顺序

这道题中,有两个遍历维度:物品与背包重量

那么问题来了,是先比哪里物品还是先遍历背包重量呢?

其实都可以,但是先遍历物品会更好理解

5.举例推导dp数组

先看看推导对应的数值,如图:

建议大家在自己的纸上推导一遍,看看每个元素是不是这样

做动态规划的题目,最好的过程就是自己在纸上推导一遍,如果推导明白了,代码写出来就算有问题,只要把dp数组打印出来,对比一下和自己推导的有什么差异,很快的可以发现问题了

以上,就是求解01背包问题,使用二维dp数组的求解过程了

一维dp数组(滚动数组)

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

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

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

所以我们发现就可以使用一维数组

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

动规五部分分析如下:

1.确定dp数组的定义

dp[j]表示:容量为j的背包,所背的物品价值可以最大为dp[j]

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

dp[j]为容量为j的背包所背的最大价值,那么如何推导dp[j]呢

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[j]表示:容量为j的背包,所背的物品价值可以最大为dp[j],那么dp[0]就是0,因为背包容量为0,所以最大容量就算0

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

这就不会被初始值覆盖了

4.一维dp数组遍历顺序

代码如下

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

需要注意的是,这里的写法,与比哪里背包的顺序是不一样的

二维数组遍历的时候,背包容量是从小到大,而一维数组遍历的时候,背包是从大到小

一维数组中倒序遍历是为了保证物品i只被放入了一次。如果是正序遍历,那么物品0就会被重复加入多次

那么问题来了,为什么二维dp数组是时候不使用倒序呢?

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

还有一个问题,不论是二维数组还是一维数组,都是遍历物品和遍历背包容量,

二维数组中可以使用遍历物品嵌套遍历背包容量,以及遍历背包容量嵌套遍历物品遍历

那么一维数组中是否可以呢?答案是不可以

一维数组中,背包容量一定要倒序遍历,所以一定是遍历物品嵌套遍历背包容量

5.举例推导dp数组

与二维dp数组一致


以上就是01背包问题求解的两种方法,接下来,让我们使用例题来进一步掌握

携带研究材料

题目描述

小明是一位科学家,他需要参加一场重要的国际科学大会,以展示自己的最新研究成果。他需要带一些研究材料,但是他的行李箱空间有限。这些研究材料包括实验设备、文献资料和实验样本等等,它们各自占据不同的空间,并且具有不同的价值。 

小明的行李空间为 N,问小明应该如何抉择,才能携带最大价值的研究材料,每种研究材料只能选择一次,并且只有选与不选两种选择,不能进行切割。

输入描述

第一行包含两个正整数,第一个整数 M 代表研究材料的种类,第二个正整数 N,代表小明的行李空间。

第二行包含 M 个正整数,代表每种研究材料的所占空间。 

第三行包含 M 个正整数,代表每种研究材料的价值。

输出描述

输出一个整数,代表小明能够携带的研究材料的最大价值。

输入示例
6 1
2 2 3 1 5 2
2 3 1 5 4 3
输出示例
5

解题思路就是按照我们之前说的基础理论部分进行推导,这里就不做过多赘述了

代码如下

#include <iostream>
#include <vector>

using namespace std;

int n, bagweight;//bagweight表示背包容量
void solve() {
	vector<int> value(n, 0);//每个物品所占空间
	vector<int> weight(n, 0);//每个物品价值
	for (int j = 0; j < n; j++)
		cin >> weight[j];
	for (int i = 0; i < n; i++)
		cin >> value[i];

	vector<vector<int>> dp(weight.size(), vector<int>(bagweight+1, 0));//定义dp数组,dp[i][j]代表行李箱空间为j情况下,从下标为[0,i]的物品中能达到的最大值

	for (int j = weight[0]; j <= bagweight; j++)//初始化,dp[i][0]已经在数组定义中被初始化为0
		dp[0][j] = value[0];
	for (int i = 1; i < weight.size(); i++) {//遍历物品
		for (int j = 0; j <= bagweight; j++) {//遍历容量
			if (j < weight[i]) dp[i][j] = dp[i - 1][j];//如果装不下就继承dp[i-1][j]的值
			else dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
			//如果装的下,就根据状态转移方程进行更新
		}
	}
	cout << dp[weight.size() - 1][bagweight] << endl;
}
int main() {
	while (cin >> n >> bagweight) {
		solve();
	}
	return 0;
}

这是二维dp数组进行推导的解题方法,下面是一维dp数组经推导的解题方法

代码如下

#include <iostream>
#include <vector>

using namespace std;

int main() {
	int M, N;
	cin >> M >> N;

	vector<int> costs(M);
	vector<int> values(M);

	for (int i = 0; i < M; i++)
		cin >> costs[i];
	for (int j = 0; j < M; j++)
		cin >> values[j];

	vector<int> dp(N + 1, 0);//初始化为0

	for (int i = 0; i < M; i++) {//遍历每个类型的研究材料
		for (int j = N; j >= costs[i]; j--) {//从N给空间开始减少当前研究材料所占空间
			dp[j] = max(dp[j], dp[j - costs[i]] + values[i]);
		}
	}

	cout << dp[N] << endl;
	return 0;
}

总结

·我们可以发现一维dp数组的写法比较直观,而且空间复杂度还降低了一个数量级

·01背包问题的理解基础就已经说完了,从dp数组定义、递推公式、初始化、遍历顺序到二维数组到一维数组都深刻的剖析了一遍,大家要将这篇内容吃透,后面再做01背包类型的题目就会很简单。我们是有着自己的思考,一步一步推导,了解了他的本质,代码的方方面面都在自己的掌握之下

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

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

相关文章

ruoyi-nbcio-plus基于vue3的flowable流程设计器主界面升级修改

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码&#xff1a; https://gitee.com/nbacheng/ruoyi-nbcio 演示地址&#xff1a;RuoYi-Nbcio后台管理系统 http://122.227.135.243:9666/ 更多nbcio-boot功能请看演示系统 gitee源代码地址 后端代码&#xff1a…

链路追踪原理

分布式系统为什么需要链路追踪&#xff1f; 随着互联网业务快速扩展&#xff0c;软件架构也日益变得复杂&#xff0c;为了适应海量用户高并发请求&#xff0c;系统中越来越多的组件开始走向分布式化&#xff0c;如单体架构拆分为微服务、服务内缓存变为分布式缓存、服务组件通…

前视声呐目标识别定位(六)-代码解析之目标截图并传输

前视声呐目标识别定位&#xff08;一&#xff09;-基础知识 前视声呐目标识别定位&#xff08;二&#xff09;-目标识别定位模块 前视声呐目标识别定位&#xff08;三&#xff09;-部署至机器人 前视声呐目标识别定位&#xff08;四&#xff09;-代码解析之启动识别模块 …

leetcode刷题-代码训练营-第7章-回溯算法1

回溯法模板 void backtracking(参数) {if (终止条件) {存放结果;return;}for (选择&#xff1a;本层集合中元素&#xff08;树中节点孩子的数量就是集合的大小&#xff09;) {处理节点;backtracking(路径&#xff0c;选择列表); // 递归回溯&#xff0c;撤销处理结果} }理解 从…

红蓝色WordPress外贸建站模板

红蓝色WordPress外贸建站模板 https://www.mymoban.com/wordpress/5.html

爬虫部署平台crawlab使用说明

Crawlab 是一个基于 Go 语言的分布式网络爬虫管理平台&#xff0c;它支持 Python、Node.js、Jar、EXE 等多种类型的爬虫。 Crawlab 提供了一个可视化的界面&#xff0c;并且可以通过简单的配置来管理和监控爬虫程序。 以下是 Crawlab 的一些主要优点&#xff1a; 集中管理&am…

DFS:深搜+回溯+剪枝解决组合问题

创作不易&#xff0c;感谢支持!!! 一、电话号码的组合 . - 力扣&#xff08;LeetCode&#xff09; class Solution { public:string hash[10]{"","","abc","def","ghi","jkl","mno","pqrs"…

2024年 前端JavaScript 进阶 第3天 笔记

3.1-JS进阶-内容和两种编程思想 3.2-构造函数实现封装以及存在 3.3-原型对象prototype 3.4-数组扩展案例-求最大值和数组求和 3.5-constructor属性以及应用 3.6-对象原型proto 3.7-原型继承 3.8-原型链以及instanceof运算符 3.9-综合案例-模态框构造函数写法 3.10-综合案例-0pe…

vtk,ITK,DICOM3.0

(14 封私信 / 80 条消息) VTK ITK OPENCV&#xff0c;从图像处理的角度来说&#xff0c;哪种用的人多&#xff1f; - 知乎 (zhihu.com) 医学领域&#xff1a;通常要求使用ITK和VTK。 ITK做底层处理算法。 VTK做可视化显示。 ITK:Insight Segment and Regestration Toolkit …

Redis的5大常见数据类型的用法

上一篇文章我们讲了Redis的10大应用场景&#xff0c;这一篇文章就针对Redis的常用数据结构进行一个说明&#xff0c;通过示例的形式演示每一种数据结构如何使用。 当涉及Redis的数据操作时&#xff0c;不同数据类型对应的不同数据结构&#xff0c;如下就对5大常用的数据类型进行…

我与C++的爱恋:内联函数,auto

​ ​ &#x1f525;个人主页&#xff1a;guoguoqiang. &#x1f525;专栏&#xff1a;我与C的爱恋 ​ 一、内联函数 1.内联函数的概念 内联函数目的是减少函数调用的开销&#xff0c;通过将每个调用点将函数展开来实现。这种方法仅适用于那些函数体小、调用频繁的函数。 …

Fusion360修改嘉立创EDA专业版生成的3D外壳文件

需要第三方软件的原因 嘉立创EDA专业版生成电路板的3D外壳文件是比较快捷的&#xff0c;但如果侧面精密开孔或者添加其它非常规的元素还是有些局限。嘉立创EDA专业版可以把3D外壳文件导出&#xff0c;这就大大方便了第三方软件的修改。 本文是利用Fusion360修改3D外壳文件&…

C++ | string类学习 | string的常见接口使用方式

目录 为什么要学习string类&#xff1f; C语言中的字符串 OOP面向对象编程 两个面试题 标准库中的string类 string类了解 string类的文档介绍 总结 string类的常用接口说明 string类对象的常见构造 string类对象的容量操作 size()和length() clear() resize(size…

【C语言】【Leetcode】2437. 有效时间的数目

文章目录 题目思路一、枚举思路二、回溯 题目 链接: link 思路一、枚举 这题的可以简单的看成 h1 h2 : m1 m2 的情况&#xff0c;其中 h1 和 h2 有关&#xff0c; m1 和 m2 有关&#xff0c;数目不多可以直接暴力枚举解决 int countTime(char * time) {int countHour 0;i…

SQLite下一代查询规划器(十)

返回&#xff1a;SQLite—系列文章目录 上一篇&#xff1a;SQLite 查询优化器概述&#xff08;九&#xff09; 下一篇&#xff1a;SQLite的架构&#xff08;十一&#xff09; 1. 引言 “查询规划器”的任务是弄清楚 找出完成 SQL 语句的最佳算法或“查询计划”。 从 SQLi…

Markdown介绍

一.Markdown基本介绍&#x1f357; Markdown 是一种轻量级标记语言&#xff0c;用于简单、易读易写的文本格式编写。它设计初衷是让人们能够使用普通文本编辑器编写格式简单的文档&#xff0c;并且可以转换成有效的HTML。Markdown 的语法非常简洁直观&#xff0c;通过使用特定…

BIT-5-动态内存管理(C语言进阶)

本章重点 为什么存在动态内存分配动态内存函数的介绍 mallocfreecallocrealloc常见的动态内存错误几个经典的笔试题柔性数组 1. 为什么存在动态内存分配 我们已经掌握的内存开辟方式有&#xff1a; int val 20;//在栈空间上开辟四个字节 char arr[10] {0};//在栈空间上开辟…

好物视频素材在哪找?视频素材大全app下载

创作优质视频内容不仅仅是一种艺术&#xff0c;也是一种科学&#xff0c;需要对素材的深刻理解和精心挑选。掌握了这些高清无水印视频素材&#xff0c;您就拥有了创作引人入胜视频内容的强大工具。以下是更多精选的视频素材网站&#xff0c;旨在为您的视频项目提供更广阔的视野…

uniapp uni.scss中使用@mixin混入,在文件引入@include 样式不生效 Error: Undefined mixin.(踩坑记录一)

问题&#xff1a; 在uni.scss文件定义mixin 2. 在vue文件引入: 3. 出现报错信息: 4. 问题思考&#xff1a; 是不是需要引入uni.scss &#xff1f; 答案不需要 uni.scss是一个特殊文件&#xff0c;在代码中无需 import 这个文件即可在scss代码中使用这里的样式变量。uni-app的…

算法day30 回溯6

332 重新安排行程 给你一份航线列表 tickets &#xff0c;其中 tickets[i] [fromi, toi] 表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。 所有这些机票都属于一个从 JFK&#xff08;肯尼迪国际机场&#xff09;出发的先生&#xff0c;所以该行程必须从 JFK …