动态规划——完全背包问题(公式推导,组合、排列)

news2024/11/25 5:57:15

        本文章是对于完全背包 一些题型(如题目所示,组合、排列和最小值类型)的总结和理解,依次记录一下,方便回顾与复习。

        本文章是基于个人所总结 实现的,但在其中遇到了一些疑惑与困难,所以总结一篇与完全背包相关的问题。

        题型分为 完全背包 求组合问题 、 求排列问题、 求最小值问题.

但这一切都是基于完全背包,我们先来介绍一下什么是完全背包。

目录

完全背包问题

二维dp

 二维优化

一维dp(滚动数组)

完全背包组合和排列问题


完全背包问题

        有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i],其价值为value[i] 。每件物品都有无限个(也就是可以放入背包多次),求解将哪些物品装入背包里物品价值总和最大。(即如何在有限的空间中 尽可能放到整体价值最高).

        完全背包和01背包问题唯一不同的地方就是,完全背包每种物品有无限件;而01背包每个物品只有一件。

        在这里我认为对你对01背包 已经有一定的了解,便不再深入赘述。如果不了解01背包,最好先了解之后再继续阅读。

二维dp

这是 01背包的核心代码

     // 二维数组
    vector<vector<int>> dp(weight.size(), vector<int>(bagweight + 1, 0));

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

    // weight数组的大小 就是物品个数
    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]);//如果大于物品体积,则取 选择当前物品 和 不选择当前物品 的最大值

        }
    }

01背包是每次从中选取一个物品 一次,即如果作选取决策的话,当前重量j 只用减去一个weight[i],所以选取后的重量是dp[i-1][j-weight[i]].

注意我下面所说的当前容量,而没有说背包总容量。因为我们是从小到大遍历的背包容量。

完全背包是 可以选取一个物品 多次(k>=0),即如果作选取决策的话当前重量j 要减去 k个weight[i],具体多少取决于当前背包容量的大小。所以我们for循环 k从0开始遍历,直至 大于当前背包容量停止。而我们也要做出抉择,即选出0-k次中最大的值,如下:

max (dp[i-1][j-weight[i] * k] + value[i]*k)

如果作不选取 决策的话,那么当前的值就不变化,继承上一次的值。即dp[i][j] = dp[i-1][j];

 所以现在状态转移方程为:

dp[i][j] = max { max (dp[i-1][j-weight[i] * k] + value[i]*k), dp[i-1][j] }. 其中(1 <= k <= j/weight[i]

代码如下:

    //n代表物品的数量,v代表背包的容量,weight[i]代表第i个物品的体积,value[i]是第i个物品的价值
    vector<vector<int>> dp(N+1,vector<int>(N+1,0));

    //遍历背包物品
    for(int i = 1; i <= n; i++)
    {  
        //遍历背包容量
        for(int j = 1; j <= v; j++)
        {
            //每次选取k件 物品i ,如果容量大于 当前容量j,则停止
            for(int k = 1; k * weight[i] <= j; k++)
            {
                //这句代码相当于max (dp[i-1][j-weight[i] * k] + value[i]*k),而由于k每次在同一行,所以每次和dp[i][j]进行比较。当然这里写max(dp[i-1],...)也没关系,因为最后都是取的 dp[i-1][j-k*weight[i]]+value[i]*k)的最大值。选dp[i-1][j]相当于每次都和第一次比较,而选dp[i][j]相当于每次都和上一次的进行比较,所以dp[i][j]最后一定是最大值
                dp[i][j] = max(dp[i][j],dp[i-1][j-k*weight[i]]+value[i]*k);
            }
            //dp[i][j] = max(dp[i-1][j],dp[i][j]); //这句代码其实不用加,因为上面第一次k一定等于0,
            //所以相当于第一次已经比较了。这里写只是为了更好的显示上面的思路
        }
    }

    return dp[n-1][v];

 二维优化

上面我们用了三重for循环,时间复杂度太高了,我们有没有办法把它转化成二维的呢?

记得上面刚说的这个状态转移方程:

一.dp[i][j] = max { max (dp[i-1][j-weight[i] * k] + value[i]*k), dp[i-1][j] }.

其中(1 <= k <= j/weight[i])

我们看到k的取值最小值为1,那我们不妨先把这 一个物品放进去

得到:

二.dp[i][j] = max {max(dp[i-1][j-weight[i]*k -weight[i]]+value[i]*k +value[i]),dp[i-1][j]);

此时k的取值范围为:(0 <= k <= j/weight[i]-1)

对于式子一,我们完全可以可以把式子简化为如下:

三.dp[i][j] = max(dp[i-1][j-weight[i] * k] + value[i]*k),其中

其中(0 <= k <= j/weight[i])

这是如何做到的呢?我们发现式子一 k的最小值是1,那么当我们让k=0时,发现dp[i-1][j-weight[i] * k] + value[i]*k)就是dp[i-1][j].所以我们完全可以合并这两个!最后只留下一个max,只不过k的最小值由1变成了0.

j=j-weight[i]时,我们将其带入到式子三:

四.dp[i][j-weight[i]] = max(dp[i-1][j-weight[i] - weight[i]*k] + value[i]*k);其中:

(0 <= k <= j/weight[i]-1)

此时我们再用式子二对比式子四:

二.dp[i][j] = max {max(dp[i-1][j-weight[i]*k -weight[i]]+value[i]*k +value[i]),dp[i-1][j]);

四.dp[i][j-weight[i]] = max(dp[i-1][j- weight[i]*k -weight[i]] + value[i]*k);

 可以发现它们是画圈的这一部分完全等价的!范围也是一样的。我们我们把式子四 替换到式子二中:

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

这就是我们最终推导出的公式!!!

所以我们再也不需要写那三重循环了,直接二维循环就可以,如下:

    vector<vector<int>> dp(N+1,vector<int>(N+1,0));
    
    //遍历背包物品
    for(int i = 1; i <= n; i++)
    {  
        //遍历背包容量
        for(int j = 0; j <= v; j++)
        {
            //如果选择的话
            if(j >= weight[i])
            dp[i][j] = max(dp[i][j-weight[i]]+value[i],dp[i-1][j]);
            //如果不选择
            else
            dp[i][j] = dp[i-1][j];
        }
    }

一维dp(滚动数组)

和01背包问题问题类似,我们同样可以转化为把  二维数组转化为一维数组,因为它们都只依赖于上一行的状态。

因此我们由

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

可以转化为一维dp数组:

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

修改完毕后,代码如下:

    vector<int> dp(N+1,0);

    for(int i = 1; i <= n; i++)
    {
        for(int j = weight[i]; j <= v; j++)
        {
            dp[j] = max(dp[j-weight[i]]+value[i],dp[j]);
        }
    }
    return dp[v];

完全背包组合和排列问题

先说结论:

利用背包 求组合数外层遍历物品,内层遍历 物品容量

利用背包求 排列数 是 想外层遍历物品的容量内层再遍历 物品.

组合 不强调顺序,而排列强调顺序

为什么是这样呢?

假设你有价值为1元 和 2元的硬币,数量分别有无限个,那么让你凑成价值为5元的方案有多少种?

这个里面,有这样的两种方案:1 2 2    2  1 2  

如果是组合数,它们将会被视为一种组合,而如果是排列数,那么它们将是两种不同的排列

具体来说:

假设你先遍历的物品数量,那么你后面只能按照固定的顺序遍历了。

假设物品有 abc,那么你也只能按照abc这个顺序来放入了. b根本没机会放到a的前面,因为遍历完a结束 才遍历b的. 所以这样是求组合数

相反的,假设你先遍历的物品容量,再遍历物品数量,这样每个物品都有机会放入背包中,顺序也不固定,假设装a不合适,我可以装b(ee,真没别的意思),随着遍历背包容量变大,说不定原来的a又适合装入了,这样就有了不同的顺序了。

大家可以在leetcode上做下面的例题感受:

求组合数:
518.零钱兑换II

求排列数:

377.组合总和IV

到这里,关于完全背包的一些基础相关的问题就讲完了,实际上还需要大家看视频或者做更多题感受到,可以看看题解中一些人发表的,他们发表的一种种类型及对应的题目可以非常好的巩固自己!

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

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

相关文章

本地数据备份与FTP远程数据迁移

数据是电脑中最重要的东西。为了保证数据安全&#xff0c;我们经常会对数据进行备份。之前一直采用将重要数据拷贝至移动硬盘的方式实现备份&#xff0c;实现简单但每次都需要把所有文件拷贝一次&#xff0c;当文件很大时效率较低。 因此&#xff0c;考虑使用 FreeFileSync 软…

【Flink系列五】Checkpoint及Barrier原理

本章内容 一致性检查点从检查点恢复状态检查点实现算法-barrier保存点Savepoint状态后端&#xff08;state backend&#xff09; 本文先设置一个前提&#xff0c;流处理的数据都是可回放的&#xff08;可以理解成消费的kafka的数据&#xff09; 一致性检查点&#xff08;che…

数据结构 | 查漏补缺之DFS、BFS、二次探测再散列法、完全二叉树、深度计算

目录 DFS&BFS 哈希表-二次探测再散列法 完全二叉树&深度计算 排序 快速排序-挖坑法 插入、选择、冒泡、区别 DFS&BFS 哈希表-二次探测再散列法 完全二叉树&深度计算 排序 快速排序-挖坑法 插入、选择、冒泡、区别 插入从第一个元素开始&#xff0c…

期末速成数据库极简版【存储过程】(5)

目录 【7】系统存储过程 【8】用户存储过程——带输出参数的存储过程 创建存储过程 存储过程调用 【9】用户存储过程——不带输出参数的存储过程 【7】系统存储过程 系统存储我们就不做过程讲解用户存储过程会考察一道大题&#xff0c;所以我们把重点放在用户存储过程。…

有一篇文章,共有3行文字,每行有80个字符。要求分别统计其中英文大写字母、小写字母、数字、空格以及其他字符的个数

目录 1解题思路&#xff1a; 2代码&#xff1a; 3运行代码&#xff1a; 4总结&#xff1a; 1解题思路&#xff1a; 有一篇文章&#xff0c;共有3行文字&#xff0c;每行有80个字符。要求分别统计其中英文大写字母、小写字母、数字、空格以及其他字符的个数 首先先设置一个字…

设计山寨线程池

作者简介&#xff1a;大家好&#xff0c;我是smart哥&#xff0c;前中兴通讯、美团架构师&#xff0c;现某互联网公司CTO 联系qq&#xff1a;184480602&#xff0c;加我进群&#xff0c;大家一起学习&#xff0c;一起进步&#xff0c;一起对抗互联网寒冬 私以为造轮子几乎是最好…

MPEG4Extractor

1、readMetaData 必须要找到 Moov box&#xff0c;找到 Mdat box或者 Moof box&#xff0c;并且创建了 ItemTable 大端 box 分为 box header 和 box content&#xff1a; box header由8个字节组成&#xff0c;前面四个字节表示这个box 的大小&#xff08;包含这个头的8字节&a…

『Linux升级路』进度条小程序

&#x1f525;博客主页&#xff1a;小王又困了 &#x1f4da;系列专栏&#xff1a;Linux &#x1f31f;人之为学&#xff0c;不日近则日退 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 目录 一、预备知识 &#x1f4d2;1.1缓冲区 &#x1f4d2;1.2回车和换行 二、倒计…

Python与ArcGIS系列(十六)重复节点检测

目录 0 简述1 实例需求2 arcpy开发脚本0 简述 在处理gis线图层和面图层数据时,有时候会遇到这种情况:数据存在重复节点或伪重复节点(两个节点距离极小),往往我们需要对这种数据进行检测标注或进行修改。本篇将介绍如何利用arcpy及arcgis的工具箱实现这个功能。 1 实例需求…

【Hadoop_02】Hadoop运行模式

1、Hadoop的scp与rsync命令&#xff08;1&#xff09;本地运行模式&#xff08;2&#xff09;完全分布式搭建【1】利用102将102的文件推到103【2】利用103将102的文件拉到103【3】利用103将102的文件拉到104 &#xff08;3&#xff09;rsync命令&#xff08;4&#xff09;xsync…

PR剪辑视频做自媒体添加字幕快速方式(简单好用的pr视频字幕模板)

如何选择合适的字幕添加进短视频呢&#xff1f;首先要先确定增加的视频风格&#xff0c;简约、商务、科技感、炫酷&#xff1b;再确定用途&#xff0c;注释、标记、语音翻译、引用、介绍&#xff1b;最后在相应的模板中挑选几个尝试&#xff0c;悬着一个最切合主题的使用&#…

【C++学习手札】基于红黑树封装模拟实现map和set

​ &#x1f3ac;慕斯主页&#xff1a;修仙—别有洞天 &#x1f49c;本文前置知识&#xff1a; 红黑树 ♈️今日夜电波&#xff1a;漂流—菅原纱由理 2:55━━━━━━️&#x1f49f;──────── 4:29 …

node js 递归生成vue文件目录

目录 什么是 fs 文件系统模块 fs.existsSync方法 方法说明&#xff1a; 语法&#xff1a; 向指定的文件中写入内容 writeFile fs.writeFile() 的语法格式 fs.writeFile() 的示例代码 判断文件是否写入成功 fs.mkdir 创建目录 目录已存在&#xff0c;重复创建 创建的目…

Linux各目录结构说明

文章目录 目录说明源码放哪里&#xff1f;拓展&#xff1a;Linux里面安装软件是装在home目录还是opt目录还是/usr/local好&#xff1f; bin boot dev etc home lib lib64 lostfound media mnt opt proc root run sbin srv sys tmp usr var 目录说明 bin 存放二进制可执行文件&…

玩转大数据11:数据可视化与交互式分析

1. 引言 数据可视化和交互式分析是大数据领域中的重要方面。随着大数据时代的到来&#xff0c;数据量越来越大&#xff0c;数据类型越来越复杂&#xff0c;传统的数据处理和分析方法已经无法满足我们的需求。数据可视化可以将复杂的数据以简单、直观的方式呈现出来&#xff0c…

素材创作平台,解决企业素材供给问题

企业对于高质量素材的需求日益增长。无论是为了提升品牌形象&#xff0c;还是为了推动产品销售&#xff0c;都需要大量的专业设计素材。然而&#xff0c;素材的获取、设计和定制往往是一项耗时耗力的工作。这时&#xff0c;美摄科技素材创作平台应运而生&#xff0c;为企业提供…

JVM 虚拟机 类的加载器分类与测试详解

Java全能学习面试指南&#xff1a;https://javaxiaobear.cn 1、类加载器的分类说明 JVM支持两种类型的类加载器&#xff0c;分别为引导类加载器&#xff08;Bootstrap ClassLoader&#xff09;和自定义类加载器&#xff08;User-Defined ClassLoader&#xff09;。 从概念上来…

webpack该如何打包

1.我们先创建一个空的大文件夹 2.打开该文件夹的终端 输入npm init -y 2.1.打开该文件夹的终端 2.2在该终端运行 npm init -y 3.安装webpack 3.1打开webpack网址 点击“中文文档” 3.2点击“指南”在点击“起步” 3.3复制基本安装图片画线的代码 4.在一开始的文件夹下在创建一…

营收增速持续放缓,博通CEO期待AI崛起救场 | 百能云芯

博通作为苹果等大型科技公司的芯片供应商&#xff0c;于周四发布了财报。尽管截至10月29日的第四季度营收增长了4%至93亿美元&#xff0c;符合市场预期&#xff0c;但增速已经降至2020年以来的最低水平。 由于企业客户和电信供应商在控制支出方面的谨慎态度&#xff0c;博通的销…

C 语言 变量

变量初始值 全局变量&#xff1a;初始值是 0 局部变量&#xff1a;初始值是 随机的 类型限定符 通常不需要显式使用 register 关键字来优化变量的存储和访问。 关键字 _Complex和_Imaginary分别用于表示复数和虚数&#xff08;二者皆是数学概念&#xff09; 变量的声明和定义 c…