01 背包(从二维数组到一维滚动数组)

news2024/11/17 8:48:51

问题描述:

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

二维dp数组01背包

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

dp[i][j] 表示从下标为[0-i] 任意组合放入背包后,背包容纳重量为j时的最大价值

2. 确定递推公式

有两个方向推出来dp[i][j]:

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

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

  • 放物品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的价值),就是背包放物品i得到的最大价值

    • j - weight[i] : 不装物品i时的容量
    • dp[i - 1][j - weight[i]] :装i时前j - weight[i]容量的最大价值。

i放进去时,那么这时候整个物品集就被分成两部分,1到i-1第i个,而这时i是确定要放进去的,那么就把j空间里的weight[i]给占据了,只剩下j-weight[i]的空间给前面i-1,那么只要这时候前面i-1j-weight[i]空间里构造出最大价值,即dp[i-1][j-weight[i]],再加上此时放入的i的价值value[i],就是dp[i][j]

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

dp[i][j] = max(不放物品,放物品)

不装i即需要到前i-1个里面选,也就是前**i-1j背包容量下的最大价值**,由于前面都已经是最优解,直接取dp[i - 1][j]就是不装i条件下的最大价值

for (int i = 1; i < weight.size(); i++){	// 遍历物品
	for (int j = 1; j <= bagweight; j++){	// 遍历背包重量
		if (j < weight[i]) {	
			dp[i][j] = dp[i - 1][j];
		}
		else{	
			dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
		}
	}
}

为什么需要if (j < weight[i])?

因为:当j-weight[i] < 0时,数组会越界,即 dp[i - 1][j - weight[i]]会报错

3. dp数组如何初始化

dp[i][j]的定义出发:

如果背包容量j0的话,即dp[i][0],无论是选取哪些物品,背包价值总和一定为0。
如果i=0的情况就是只有一个物品0,即dp[0][j],存放编号0的物品的时候,各个容量的背包所能存放的最大价值。

  • j < weight[0],dp[0][j] 应该是 0,因为背包容量比编号0的物品重量还小。
  • j >= weight[0]时,dp[0][j] 应该是value[0],因为背包容量放足够放编号0物品。

在这里插入图片描述
这里只列举初始化dp[0][j](因为构造vector的时候,基本上都已经初始化元素为0了)

    // 初始化
    for (int j = weight[0]; j <= bagweight; j++) {
        dp[0][j] = value[0];
    }

4. 确定遍历顺序

先遍历物品,后遍历重量

// weight数组的大小 就是物品个数
for(int i = 1; i < weight.size(); i++) { // 遍历物品(已经初始化dp[0][j]的情况了)
    for(int j = 1; j <= bagweight; j++) { // 遍历背包容量(已经初始化dp[i][0]的情况了)
		// ...
    }
}

5. 举例推导dp数组

在这里插入图片描述
在这里插入图片描述

完整代码:

#include <iostream>
#include <vector> 
#include <algorithm> 
using namespace std;

int bag(vector<int> weight, vector<int> value, int bagweight) {
	vector<vector<int>> dp(weight.size(), vector<int>(bagweight + 1, 0));

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

	for (int i = 1; i < weight.size(); i++){
		for (int j = 1; j <= bagweight; j++){
			if (j < weight[i]){
				dp[i][j] = dp[i - 1][j];
			}
			else{
				dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
			}
		}
	}

	for (auto x : dp){
		for (auto xx : x){
			cout << xx << " ";
		}
		cout << endl;
	}
	return dp[weight.size() - 1][bagweight];
}
int main(){
	vector<int> weight = { 1, 3, 4 };	
	vector<int> value = { 15, 20, 30 };
	int bagweight = 4;
	bag(weight, value, bagweight);
	system("PAUSE");
	return 0;
}

一维滚动dp数组01背包

为什么可以将二维数组dp[i][j]转化为dp[i]呢?

二维数组的递推公式:dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
从上面公式可以看出,当前层i的数据都是由上一层i-1推导出来的。

那是否可以直接将上一层i-1的数据直接拷贝到当前层i上,直接进行计算呢?

答案是可以的,通过观察发现,dp[i][j]的数据只与正上方的值dp[i-1][j]左上角的值dp[i-1][j-weight[i]]这两个数据有关,而与右边的数据无关,那么从右向左遍历,遍历时左边的数据还是上一行的数据没有更新, 这样子用一行数组很好的实现了我们的最终目的。

dp[2][4] = max(dp[1][4], dp[1][0] + value[2]);

  • 正上方的值:35
  • 左上角的值:0 + 30
  • 最终值:max(35,30)

在这里插入图片描述
dp[1][4] = max(dp[0][4], dp[0][1] + value[1]);

  • 正上方的值:15
  • 左上角的值:15 + 20
  • 最终值:max(15,35)

在这里插入图片描述
dp[2][3] = dp[1][3] ,由于 j < weight[2]
在这里插入图片描述

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

dp[j] 表示从容量为j的背包所背的最大价值

2. 确定递推公式

有两个方向推出来dp[j]:

  • 不放物品i:取上一层的数值(即二维数组中正上方的数值),滚动数组只有不更新,就一直是上一层的数值,所以取dp[j]即可
  • 放物品idp[j - weight[i]] + value[i]表示 容量为【j - 物品i重量 】的背包 加上 【物品i】的价值。此时就是容量为j的背包所背的最大价值

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

**可以看出相对于二维dp数组的写法,就是把dp[i][j]i的维度去掉了。**ps:实在忘记公式了,可以直接先写出二维的,再把递归公式中的i去掉

3. dp数组如何初始化

dp[0] = 0; 背包容量为0所背的物品的最大价值就是0。

4. 确定遍历顺序

一维dp数组遍历顺序:

for(int i = 0; i < weight.size(); i++) { // 遍历物品
    for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量(从大到小)
        // ..
    }
}

为什么遍历背包容量的时候是从大到小呢?

为了避免前面的值会对后面的更新造成影响;
因为在二维数组写法中,每行值的更新依赖于上一行的值(正上方,左上角);
而一维dp数组中每个值的更新依赖于还没被更新的前面元素的值。

换个说法,一维dp数组中每个值的更新依赖于上次循环更新过且本次循环还未更新过的前面元素的值。
一维数组写法相当于二维数组写法每行数据拷贝给下一行用以下一行的数据更新。

所以在一维dp数组中如果正序遍历,我们如果遍历到dp[j]dp[j]左边或者说二维dp数组左上方的值就已经被修改了。那么算出来的max值肯定是不对的

5. 举例推导dp数组

一维dp,分别用物品0,物品1,物品2 来遍历背包,最终得到结果如下:
在这里插入图片描述

完整代码:

#include <iostream>
#include <vector> 
#include <algorithm> 
using namespace std;

int bag(vector<int> weight, vector<int> value, int bagweight) {
	vector<int> dp[bagweight + 1];	// 一维滚动数组

	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]);
		}
	}
	return dp[bagweight];
}

int main(){
	vector<int> weight = { 1, 3, 4 };	// 已经排序好了
	vector<int> value = { 15, 20, 30 };
	int bagweight = 4;
	bag(weight, value, bagweight);
	cout << ret << endl;
	system("PAUSE");
	return 0;
}

参考文章:

01背包理论基础

01背包理论基础(滚动数组)

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

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

相关文章

Stable Diffusion写真完整教程

前言 最近自己对AI非常痴迷&#xff0c;并且今后也会一直在这个领域深耕&#xff0c;所以就想着先入门&#xff0c;因此花时间研究了一番&#xff0c;还好&#xff0c;出了点小成果&#xff0c;接下来给大家汇报一下。 AI绘画 提到AI绘画&#xff0c;大家可能立马会想到made…

男款内裤哪个品牌最好?男士内裤高能测评,选购攻略分享!

很多男性朋友的内裤都是穿到天荒地老的存在&#xff0c;但实际上如果一条内裤没有定期更换的话&#xff0c;存在的细菌就难以消除&#xff0c;从而可能导致出现健康问题&#xff0c;而且一条内裤没有定期更换&#xff0c;舒适性和透气性等方面都非常差&#xff01; 定期更换内裤…

AI 入门:从 ChatGPT 开始

在信息泛滥的时代&#xff0c;AI 技术已经渗透到生活的各个方面&#xff0c;学习 AI 成为个人发展的必然趋势。而 ChatGPT 作为 AI 领域的佼佼者&#xff0c;无疑是开启学习之旅的最佳起点。它不仅提供了一个便捷的交流平台&#xff0c;更能显著提升信息处理效率。 本文将带领…

在 Python 的哪个版本之后,字典的添加顺序与键的顺序是一致的?

&#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ 在 Python 的不同版本中&#xff0c;字典&#xff08;dict&#xff09;类型的行为发生了显著变化。在 Python 3.6 及之前的版本中&#xff0c;字典是无序的&#xff0c;这意味着字典在遍历时不能保证按…

硬盘架构原理及其算法RAID工作原理写惩罚

一、硬盘的架构以及寻址原理 硬盘工作原理&#xff1a; 硬盘寻址原理&#xff1a;逻辑顺序磁道、盘片、扇区&#xff08;顺序CHS&#xff09; 二、机械硬盘算法 读取算法 寻道算法 个人与企业适合的算法和寻道 个人使用的机械硬盘适合的寻道算法和读取算法是&#xff1a…

[Vision Board创客营]--使用openmv识别阿尼亚

文章目录 [Vision Board创客营]使用openmv识别阿尼亚介绍环境搭建训练模型上传图片生成模型 使用结语 [Vision Board创客营]使用openmv识别阿尼亚 &#x1f680;&#x1f680;五一和女朋友去看了《间谍过家家 代号&#xff1a;白》&#xff0c;入坑二刺螈&#xff08;QQ头像也换…

杰发科技AC7840——软件Sent_HAL39X

0. 序 使用PWM模拟Sent测试下7840的软件sent功能。 参考链接&#xff1a;SENT协议应用笔记 - TechPlus汽车工坊的文章 - 知乎 SENT协议 1. Sent功能测试 使用提供的软件Sent代码在7840上测试&#xff0c;接收数据OK 2. 参考资料 3. 数据解析 我们个根据上述参考资料尝试解析…

商务英语口语成人考级外语培训之BECkao考级口语篇

在口语考试中&#xff0c;不管实际内容你能说出多少&#xff0c;但准备一些套话&#xff0c;至少还能撑撑场子你们说是不是&#xff1f; 内容阐述 描述事实 1.Im going to describe/present/explain/give you some information about... 2.Id like to say a few words about...…

【Go】Go Swagger 生成和转 openapi 3.0.3

本文档主要描述在 gin 框架下用 gin-swagger 生成 swagger.json 的内容&#xff0c;中间猜的坑。以及&#xff0c;如何把 swagger 2.0 转成 openapi 3.0.3 下面操作均在项目根目录下执行 生成 swagger 2.0 import swagger go get -u github.com/swaggo/gin-swagger go get …

提高静态住宅代理稳定性妙招

在数字化时代的浪潮中&#xff0c;静态住宅代理因其独特的优势&#xff0c;如固定的IP地址、更高的隐私保护性等&#xff0c;逐渐成为网络爬虫、数据分析等领域不可或缺的工具。然而&#xff0c;静态住宅代理的稳定性问题一直是用户关注的焦点。本文将为您揭示提高静态住宅代理…

分布式任务调度框架xxl-job使用手册

官网地址和文档地址&#xff1a;https://www.xuxueli.com/xxl-job/ 一、快速入门 1.1 下载源码 https://github.com/xuxueli/xxl-job https://gitee.com/xuxueli0323/xxl-job 下载完成后有以下模块 1.2 初始化数据库 官方指定mysql8.0&#xff0c;但我是mysql5.7 执行/xxl…

C++相关概念和易错语法(12)(迭代器、string容量调整)

1.迭代器&#xff08;以string为例&#xff09; &#xff08;1&#xff09;基本理解&#xff1a;在我们刚接触迭代器的时候&#xff0c;我们可以将迭代器理解为改造过的“指针”&#xff0c;这是一个新的类型&#xff0c;指向对应容器中的各个元素。我们可以像指针那样对迭代器…

朋友圈刷屏的粘土风格照片,你体验过了吗?

Remini 的粘土风格真的丑萌丑萌的&#xff01; 从去年“妙鸭相机”的走红&#xff0c;到今年Remini的刷屏&#xff0c;其实可以看出大众对于图片趣玩的兴趣非常大&#xff01; 一张普通的照片经过工具的处理&#xff0c;一下子变成新风格&#xff0c;让人眼前一亮。如果你也对…

【代码分享】使用HTML5的Canvas绘制编码说明图片

最急在工作中遇到一个需求&#xff0c;根据给定的编码生成编码说明&#xff0c;像下面这样的效果。 不同含义的编码用横杠分割&#xff0c;然后每个编码下面用箭头指明具体的含义。下面是我使用canvas实现的代码。具体的编码宽度大家可以根据实际情况进行调整&#xff0c;目前…

Excel如何设置密码保护【图文详情】

文章目录 前言一、Excel如何设置密码保护&#xff1f;二、Excel如何取消密码保护&#xff1f;总结 前言 在软件项目开发过程中&#xff0c;会输出很多技术文档&#xff0c;其中也包括保密级别很高的服务器账号Excel文档。为了确保服务器账号相关的Excel文档的安全性&#xff0…

Python经典案例爬取豆瓣Top250电影数据

随着网络数据的日益丰富&#xff0c;如何从海量的信息中快速、准确地提取出有价值的数据&#xff0c;成为了许多开发者和技术爱好者关注的焦点。在这个过程中&#xff0c;网络爬虫技术凭借其强大的数据获取能力&#xff0c;成为了数据分析和挖掘的重要工具。本文将通过一个经典…

二叉树进阶 --- 上

目录 1. 二叉搜索树的概念及结构 1.1. 二叉搜索树的概念 1.2. 二叉搜索树的结构样例 2. 二叉搜索树的实现 2.1. insert 的非递归实现 2.2. find 的非递归实现 2.3. erase 的非递归实现 2.3.1. 第一种情况&#xff1a;所删除的节点的左孩子为空 2.3.1.1. 错误的代码 2…

[QNX] BSP 网络性能优化:调优io-pkt和ClockPeriod提升网速

0 概要 本文介绍如何在QNX系统上优化网络性能&#xff0c;主要通过调整io-pkt和ClockPeriod参数来实现。通过优化&#xff0c;网络吞吐量可以得到显著提升。 1 优化方法 1.1 调整io-pkt的mclbytes参数: io-pkt是QNX系统中常用的网络协议栈&#xff0c;其mclbytes参数指定了…

一、精准化测试介绍

精准化测试介绍 一、精准化测试是什么&#xff1f;二、什么是代码插桩&#xff1f;三、两种插桩方式Offine模式&#xff1a;On-the-fly插桩: 四、jacoco覆盖率报告展示五、增量代码覆盖率监控原理六、精准测试系统架构图七、全量与增量覆盖率报告包维度对比八、全量与增量覆盖率…

视频断点上传

什么是断点续传 通常视频文件都比较大&#xff0c;所以对于媒资系统上传文件的需求要满足大文件的上传要求。http协议本身对上传文件大小没有限制&#xff0c;但是客户的网络环境质量、电脑硬件环境等参差不齐&#xff0c;如果一个大文件快上传完了网断了没有上传完成&#xf…