C++ 背包理论基础01 + 滚动数组

news2024/9/24 7:24:15

背包问题的重中之重是01背包

01背包

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

每一件物品其实只有两个状态,取或者不取,所以可以使用回溯法搜索出所有的情况,那么时间复杂度就是o(2^n),这里的n表示物品数量。

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

举一个例子:背包最大重量为4,有3个物品,其重量和价值如下表

物品为:

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

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

动规五部曲

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

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

2)递推公式

对于每一个物品都有放与不放两种状态,所以又有两种情况,取这两种情况的最大值

3)dp数组初始化

根据递推公式,可知dp数组是由其左上角和正上方推导而来,所以要对其第一行和第一列进行初始化

其他下标的价值由递推公式更新推导,所以其它地方初始化为任意值均可

4)遍历顺序

因为本题有两个维度,分别为物品维度和背包容量维度,所以到底是先遍历物品呢?还是先遍历背包呢?

可以分类讨论一下

先遍历物品

先遍历背包

5)打印dp数组

代码

int weight_bag(vector<int> weight,vector<int> value,int bagweight){
    vector<vector<int>> dp(weight.size(),vector<int>(bagweight+1,0));
    //初始化.对于物品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=0;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]);
        }
    }
    return dp[weight.size()-1][bagweight];
}

01背包(滚动数组)

dp[i - 1]这一层拷贝到dp[i]上,只用一个一维数组dp[j],可以理解是一个滚动数组。

滚动数组需要满足的条件是上一层可以重复利用,直接拷贝到当前层,一直处于一种刷新的状态

动规五部曲

1)确定dp数组的定义

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

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

dp[j]有两个选择,一个是不放物品i,取自己dp[j] 相当于 二维dp数组中的dp[i-1][j];一个是放物品i,取dp[j - weight[i]] + value[i],取二者当中的最大值

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

3)一维dp数组的初始化

dp[0]就应该是0,因为背包容量为0所背的物品的最大价值就是0。

除了下标0的位置,初始为0,其他下标应该初始化多少呢?

根据递推公式,dp数组在推导的时候一定是取价值最大的数,那么非0下标都初始化为0就可以了,这样才能让dp数组在递归公式的过程中取的最大的价值,而不是被初始值覆盖了

4)一维dp数组的遍历顺序

一维dp遍历的时候,背包是从大到小,注意一定要是倒序遍历

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

因为根据递推公式,每个物品只放一次,在遍历物品的时候,变动j时,每次都要刷新dp[j]

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

dp[2]=dp[2-weight[0]]+value[0]=dp[1]+value[0]=15+15=30,这里物品0会放入两次,因为前面刷新了dp[1],初始值会被修改,连带着后面的dp[2]会受到波及!!!!

为什么二维dp数组遍历的时候不用倒序呢?

因为对于二维dp,dp[i][j]都是通过上一层即dp[i - 1][j]计算而来,本层的dp[i][j]并不会被覆盖!,因为是二维数组,没有动态刷新,所以一旦求出数值,便会一直在那保持不变

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

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

因为是每次只对一个背包容量进行求解,

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

dp[4]=max(15,dp[4-3]+value[1])=max(15,20)=20

dp[4]=max(10,dp[4-4]+value(2))=max(20,30)=30

dp[3]=dp[3-1]+value(0)=value(0)=15

........

如上,因为是倒序遍历和初始化的存在,针对每个背包容量,每次都只放进了一个物品,不能将两个或多个物品的情况考虑在内


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

dp[3]=max(dp[3],dp[3-1]+value[0])=15

dp[2]=max(dp[2],dp[2-1]+value[0])=15

dp[1]=max(dp[1],dp[1-1]+value[0])=15

dp[4]=max(dp[4],dp[4-3]+value[1])=max(15,15+20)=35

.........

如上,每个背包的容量随着物品的变动与前面每一个物品的情况结合起来,这才是正解

倒序遍历的原因是,本质上还是一个对二维数组的遍历,并且右下角的值依赖上一层左上角的值,因此需要保证左边的值仍然是上一层的,从右向左覆盖。

倾向于使用一维dp数组的写法,比较直观简洁,而且空间复杂度还降了一个数量级

代码

int  weightvalue(int bagweight,vector<int>weight,vector<int>value){
 //初始化dp数组
    vector<int> dp(bagweight+1,0);
    //递推
    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];
}

背包问题应用1

题目:416分割等和子集

题目链接:分割等和子集

对题目的理解

非空数组由正整数组成,是否可以将这个数组分割成两个子集,使得两个子集的元素和相等

将问题具象化:只要找到集合里能够出现 sum / 2 的子集总和,就算是可以分割成两个相同元素和子集了。

集合中的元素只能使用一次,因此使用的是01背包

只有确定了如下四点,才能把01背包问题套到本题上来。

  • 背包的容量为sum / 2
  • 背包要放入的商品(集合里的元素)重量为nums[i],价值也为nums[i]
  • 背包如果正好装满,说明找到了总和为 sum / 2 的子集。
  • 背包中每一个元素是不可重复放入

动规五部曲

1)确定dp数组含义及下标

dp[j]:容量为j的背包所背的最大价值为dp[j]

集合中的每个元素既是重量也是价值   dp[target]==target 背包装满 其中target=sum/2

2)递推公式

背包的递推公式:dp[j]=max(dp[j],dp[j-weight[i]]+value[i])

所以本题的递推公式:dp[j]=max(dp[j],dp[j-nums[i]]+nums[i])

3)dp数组初始化

dp[0]=0   ,非零下标进行初始化,dp[i]=0,根据递推公式,不能初始化别的值,初始化为非负整数的最小值,即0,这样才能让dp数组在递推的过程中取得最大的价值,而不是被初始值覆盖了

4)遍历顺序

遍历顺序:先正序遍历物品,后倒序遍历背包容量

for(i=0;i<nums.size(i++)){

     for(j=target;j>=nums[i];j--)}

5)打印dp数组

代码

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        vector<int> dp(10001,0);//定义dp数组,并将其初始化为0,因为数组中的元素最大是100,且至少有200个数那么最大的重量为200*100=20000,背包中只有一半容量就可以了
        int sum = 0;
        for(int i=0;i<nums.size();i++){
            sum += nums[i];
        }
        if(sum % 2==1) return false;//如果和的一半是奇数,那么不可能有两个子集和相等
        int target = sum/2;
        //递推公式
        //先正序遍历物品,后倒序遍历背包
        for(int i=0;i<nums.size();i++){
            for(int j=target;j>=nums[i];j--){
                dp[j]=max(dp[j],dp[j-nums[i]]+nums[i]);
            }
        }
        if(dp[target]==target) return true;
        return false;
    }
};

  • 时间复杂度:O(n^2)
  • 空间复杂度:O(n),虽然dp数组大小为一个常数,但是大常数

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

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

相关文章

E云管家开发自动转发朋友圈

简要描述&#xff1a; 转发朋友圈&#xff0c;直接xml数据。(对谁不可见) 请求URL&#xff1a; http://域名地址/forwardSns 请求方式&#xff1a; POST 请求头Headers&#xff1a; Content-Type&#xff1a;application/jsonAuthorization&#xff1a;login接口返回 参…

手机便签app哪个比较好用?

手机便签类软件的种类是比较多的&#xff0c;不管是安卓手机品牌还是苹果手机品牌的手机&#xff0c;在手机的应用商店中搜索“便签”&#xff0c;大家会找到很多便签类软件。那么&#xff0c;手机便签APP哪个比较好用呢&#xff1f; 在选择手机便签APP时&#xff0c;大家比较…

STM32F103C8T6第7天:

1. 智能小车&#xff1a;让小车动起来&#xff08;360.64&#xff09; 硬件接线 B-2A – PB0B-1A – PB1A-1B – PB2A-1A – PB10其余接线参考上官一号小车项目。 cubemx配置 代码&#xff08;28.smartCar_project1/MDK-ARM&#xff09; 2. 智能小车&#xff1a;串口控制小…

python爬虫进阶教程之如何正确的使用cookie

文章目录 前言一、获取cookie二、程序实现三、动态获取cookie四、其他关于Python爬虫技术储备一、Python所有方向的学习路线二、Python基础学习视频三、精品Python学习书籍四、Python工具包项目源码合集①Python工具包②Python实战案例③Python小游戏源码五、面试资料六、Pytho…

自学成为android framework工程师需要准备哪些装备-千里马车载车机系统开发学习

背景 hi&#xff0c;粉丝朋友们&#xff1a; 大家好&#xff01;经常有很多学员买课同学都会问到需要准备哪些装备&#xff0c;我也回答了很多学员了&#xff0c;今天就搞一篇文章来统一说明一下&#xff0c;告诉一下大家如果你想从一个framework新手变成一个framework开发的高…

微服务--04--SpringCloudGateway 网关

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 1.网关路由1.1 认识网关在SpringCloud当中&#xff0c;提供了两种网关实现方案&#xff1a; 1.2.快速入门1.3.路由过滤 2.网关登录校验2.1.鉴权思路分析2.2.网关过滤…

vue+echarts实现依赖关系无向网络拓扑结图节点折叠展开策略

目录 引言 一、设计 1. 树状图&#xff08;不方便呈现节点之间的关系&#xff0c;次要考虑&#xff09; 2. 力引导依赖关系图 二、力引导关系图 三、如何实现节点的Open Or Fold 1. 设计逻辑 节点展开细节 节点收缩细节 代码实现 四、结果呈现 五、完整代码 引言 我…

使用Kafka、Flink、Druid构建实时数据系统架构

1. 背景 对于很多数据团队来说&#xff0c;要满足实时需求并不容易。为什么&#xff1f;因为作流程&#xff08;数据采集、预处理、分析、结果保存&#xff09;涉及大量等待。等待数据发送到 ETL 工具&#xff0c;等待数据批量处理&#xff0c;等待数据加载到数据仓库中&#…

数据库的增删查改(CRUD)基础版

CRUD: create增加、retrieve查询、update更新、delete删除 注意一点&#xff1a;MySQL对大小写是不敏感的 目录 新增&#xff08;create&#xff09; 全列插入 指定列插入 多行插入 查询&#xff08;Retrieve&#xff09; 列查询 全列查询 指定列查询 表达式查询 …

【蓝桥杯 LCA 差分】 砍树

题目分析&#xff1a; 这道题还是比较裸的一道书上差分的题目了 对于每一对标记点(x,y) 他们之间的路径就是 x − > L C A ( x , y ) − > y x->LCA(x,y)->y x−>LCA(x,y)−>y 这条路径上的每一条边都要经过。 那么对于一条边&#xff0c;什么时候砍掉这条…

网工内推 | 外企网工,五险一金,弹性工作,最高30k*14薪

01 金蝶软件&#xff08;中国&#xff09;有限公司 招聘岗位&#xff1a;网络工程师 职责描述&#xff1a; 1、合理规划公司网络&#xff0c;保障网络架构的合理性、可靠性及前瞻性&#xff1b; 2、负责公司网络运维&#xff0c;处理日常运维事件&#xff0c;保障网络的稳定可…

Leetcode算法系列| 3. 无重复字符的最长子串

目录 1.题目2.题解C# 解法一&#xff1a;滑动窗口算法C# 解法二&#xff1a;索引寻找Java 解法一&#xff1a;滑动窗口算法Java 解法二&#xff1a;遍历字符串 1.题目 给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的 最长子串 的长度。 示例1: 输入: s "ab…

springboot中4级配置文件优先级

springboot中4级配置文件优先级

C++STL库常用详解与原理

CSTL库 学习方法&#xff1a;使用STL的三个境界&#xff1a;能用&#xff0c;明理&#xff0c;能扩展。 常用库 库名称所需头文件数据结构string#include<string>串vector#include<vector>动态数组list#include<list>带头双向循环链表queue#include<queu…

双馈风机频率二次跌落,永磁风机一次调频火电水电光伏储能直流一次调频,虚拟惯性下垂控制,虚拟同步机VSG控制,二次调频也可继续深入研究

双馈风机(永磁同步风机)虚拟惯性控制下垂控制参与系统一次调频的Matlab/Simulink模型&#xff0c;调频结束后转速回复&#xff0c;造成频率二次跌落SFD。 系统为三机九节点模型&#xff08;可更换为四机两区域&#xff0c;十机39节点&#xff0c;IEEE39节点&#xff0c;IEEE11…

WIN10 x86环境部署ARM虚拟机(银河麒麟)

我们经常使用的是x86架构的cpu&#xff0c;而对于不同cpu架构的arm架构的操作系统&#xff0c;我们可以通过QEMU模拟器来进行模拟一个arm环境 1、部署前的准备 arm的镜像&#xff1a; 以此镜像为例&#xff1a;Kylin-Server-10-SP2-aarch64-Release-Build09-20210524.iso QE…

Codeforces Round #911 (Div. 2)

A.Cover in Water 题意&#xff1a; 有一个 1 n 1 \times n 1n的水池&#xff0c;里面有些格子可以加水&#xff0c;有些格子是被堵上的&#xff0c;你可以进行以下两种操作&#xff1a; 1.往一个空的格子里加水 2.移除一个有水的格子中的水&#xff0c;并将这些水添加到另…

智能优化算法应用:基于混合蛙跳算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于混合蛙跳算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于混合蛙跳算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.混合蛙跳算法4.实验参数设定5.算法结果6.参考…

Selenium+Python做web端自动化测试框架与实例详解教程

最近受到万点暴击&#xff0c;由于公司业务出现问题&#xff0c;工作任务没那么繁重&#xff0c;有时间摸索seleniumpython自动化测试&#xff0c;结合网上查到的资料自己编写出适合web自动化测试的框架&#xff0c;由于本人也是刚刚开始学习python&#xff0c;这套自动化框架目…