对01背包的分析与理解(图文)

 

首先谢谢Christal_R的文章(点击转到链接)让我学会01背包

本文较长,但是长也意味着比较详细,希望您可以耐心读完。

题目:

现在有一个背包(容器),它的体积(容量)为V,现在有N种物品(每个物品只有一个),每个物品的价值W[i]和占用空间C[i]都会由输入给出,现在问这个背包最多能携带总价值多少的物品?

一.动态规划与递推解决01背包

初步分析:

0. 浅谈问题的分解

在处理到第i个物品时,可以假设一共只有i个物品,如果前面i-1个物品的总的最大价值已经定下来了,那么第i个物品选不选将决定这1~i个物品能带来的总的最大价值

刚刚是自顶向下,接下来反过来自底向上,第1个物品选不选可以轻松地用初始化解决,接下来处理第i个物品时,假设只有2个物品就好,那他处理完后前2个物品能带来的最大总价值就确定了,这样一直推下去,就可以推出前n个物品处理完后能带来的最大总价值

 

1.分层考虑解决"每个物品最多只能装一次"

每个物品只能装一次,那么就应该想到常用的一种方法,就是用数组的纵轴来解决,对于n个物品,为它赋予i=1~n的编号,那么数组的纵轴就有n层,每层只考虑装不装这个物品,那么分层考虑就可以解决最多装一个的问题了

 

2.对0,1的理解

对于每个背包,都只有0和1的情况,也就是拿或者不拿两种情况

如果拿:那么空间就会减一点,比如说现在在考虑第i个物品拿不拿,如果说当前剩余空间为j,那么拿了之后空间就变为j-c[i],但是总价值却会增加一点,也就是增加w[i]

如果不拿:那么空间不会变,还是j,但是总价值也不会变化

 

3.限制条件

所以对于这题来说有一个限制条件,就是空间不超出,然后目标就是在空间不超出的情况塞入物品使总价值最大,在前面,我们已经讲了数组的纵轴用来表示当前处理到第几个物品,那么只靠这个是不够的,而且这个数组的意义还没有讲

这题就是限制条件(空间)与价值的平衡,你往背包中塞东西,价值多了,可是空间少了,这空间本来可能遇到性价比更高的物品但也可能没遇到

4.具体的建立数组解决问题

有了前面的限制情况和0,1的分析就可以建立数组了

对于这个数组,结合题目要求来说,数组的意义肯定是当前的总价值,也就是第i个物品的总价值,那么题目还有一个限制条件,只靠一个n层的一维数组是不够的,还需要二维数组的横轴来分析当前的剩余容量

所以我们有了一个数组可以来解决问题了,这个数组就叫f好了,然后它是一个二维数组,它的纵轴有i层,我希望它从i=1~n,不想从下标0开始是为了美观,然后这个二维数组的横轴代表着当前剩余的空间,就用j来表示,j=0~V,0就是没有空间的意思,V前面说了,是这个背包的总容量

我们把这个二维数组建立在int main()的上面,所以它一开始全部都是0,省去了接下来赋初值为0的功夫

有了数组f[i][j],然后对于每个f[i][j],它表示的是已经处理到第i个物品了,当剩余空间还有j时,能带有的最大价值,也就是说f[i][j]存储的是总价值

说是总价值,可是涉及到放物品还是不放物品的问题,所以再细致点就是:当前剩余空间为j,用这j空间取分析第i个物品装不装如,处理执行完行为后,f[i][j]就表示了当前能装入的最大价值

 

5.推导递推方程

PS:谈一下对于动态规划递推的理解:处理到第i层时,假设前i-1层的数据都知道而且可以根据1~i-1层的数据推出i,那么就成功了一半了,因为第i层如此,那么第i-1层也可以根据1~i-2层推出,接下来只需要定义好数组的初始条件和注意边缘问题以及一些细节就可以了

对于第i个物品,假设前i-1个物品都已经处理完

如果第i个物品不能放入:这种情况就是背包已经满了,也就是当前剩余空间j小于第i个物品的占用空间C[i],

这种情况下,空间没有变化,价值也没有变化,对于空间没有变化,即第i个物品的空间和第i-1个物品的空间j相同,对于价值没有变化,也就是数组f的值相同,然后开始利用前面的数据,也就是f[i][j]]=f[i-1][j]

 

如果第i个物品不想放入,那么和不能放入其实是一样的,动机不同但结果相同,f[i][j]]=f[i-1][j]

 

如果第i个物品放入了,那么f[i][j]=f[i-1][j-c[[i]]+w[i],下面解释一下这个公式,第i个物品的占用空间为c[i],价值为w[i],f[i-1][j-c[[i]]+w[i]表示前i-1个物品在给它们j-c[[i]空间时能带来的最大价值

再回到第i个物品的角度,此时有j个空间,如果已经确定要放入,为了使空间充分利用,肯定是这j个空间只分c[i](刚好够塞下第i个物品),剩下的j-c[[i]全部给前面i-1个物品自由发挥,反正前面f[i-1][j-c[[i]]已经知道了,然后前面i-1个物品用j-c[i]的空间能带来最大的利益f[i-1][j-c[[i]],第i个物品用c[i]的空间带来利益w[i],所以如果第i个物品放入后,总利益是f[i][j]=f[i-1][j-c[[i]]+w[i]

 

但是,长远来说,有一些偏极端情况,放入这个物品,也许它价值w[i]很高,但是它占用空间c[i]也大,它的性价比可能很低,所以这时候就需要max函数了

当还有空间时:F[i,j] = max[F[i−1,j],F[i−1,j−C[i]] + W[i]

当空间不够时:F[i,j] = F[i−1,j]

下面一个个解释:

当还有空间时:这时有两种方法,放还是不放,如果放,那么利益由两段组成1~i-1是一段,i是另一段;如果不妨,那么利益和上一层剩j空间时相同,这两个东西大小需要比较,因为如果放入,虽然加上了w[i],利益,可是冲击了前i-1个物品的利益,如果不放,那么没有收获到第i个物品的利益,但是把原来属于1~i的空间j,分给了1~i-1个物品,说不定前1~i-1的每个物品都空间小,价值高,性价比高呢?

当空间不够时,它也只能F[i,j] = F[i−1,j]了,没有选择的余地

 

#include<bits/stdc++.h>//万能头文件
#define ll long long
using namespace std;
const ll maxn=100;
ll n,v,f[maxn][maxn];
ll c[maxn];//每个物品占用空间
ll w[maxn];//每个物品的价值
int main()
{cin>>n>>v;for(ll i=1;i<=n;i++)scanf("%lld",&c[i]);for(ll i=1;i<=n;i++)scanf("%lld",&w[i]);for(ll i=1;i<=n;i++)//第i个物品for(ll j=v;j>=0;j--)//剩余空间j
        {if(j >= c[i])//如果装得下f[i][j]=max( f[i-1][j-c[i]]+w[i],f[i-1][j]);else//如果装不下f[i][j]=f[i-1][j];}cout<<f[n][v]<<endl;//输出答案

}
01背包普通版代码
点击加号展开代码,如果点不开可以看底下的代码

 

 

二.01背包的空间优化

有了前面基础版01背包的学习,现在学习这个就容易多了

1.何为空间优化,为什么要空间优化

在01背包中通过对数组的优化(用了滚动数组的方法),可以使本来N*V的空间复杂度降低V,也就是把关于第几个物品的N去掉了(下面会解释为什么可以这么做)

至于为什么要空间优化,首先是因为递推本来就是用空间换时间,消耗的空间比较大,然后关于算法的竞赛一般都会有空间的限制要求,最后,在找工作面试时,面试官肯定会问一些优化的问题,平时养成优化的习惯面试时也有好处

2.为什么这题可以降维

通过观察可以发现对于普通版的01背包递推式,f[i][...]只和f[i-1][...]有关,那么我们可以用一种占用,一种滚动的方法来循环使用数组的空间,所以这个方法叫滚动数组,对于将来肯定用不到的数据,直接滚动覆盖即可,具体的如何滚动会放下面讲

还有就是滚动数组的缺点牺牲了抹除了大量数据,不是每道题都可以用,但是在这,答案刚好是递推的最后一步,所以直接输出即可,递推完后不需要调用那些已经没了的数据,所以这题可以

下面先画个图理解一下滚动的大致概念

反正就是不断覆盖的过程

3.这题如何具体优化

下面开始具体化的分析

对于第i层,它只和第i-1层有关,但是对于剩余空间j无法优化,所以现在拿i开刀,把他砍掉,用一个长度为V(总空间)的数组来表示,然后每次相邻的两个i和i-1在上面一直滚动

所以现在建立一个数组f[V],一维数组大小为V

首先建立两个复合for循环

for(i=1~n)

  for(j=v~0)

记住这里第二层循环必须是v~0而不是0~v,先记着,后面会解释,

接下来的分析建议配合下面图片学习

然后在循环的过程中,还是老样子,假设我们已经循环到i=2这层了(也就是说i=1已经循环完了),然后对于i=2这一层,我们对j循环,j从v到0

假如现在j=v,我们让f[j]=max(f[j],f[j-c[i]]+w[i])

在没有覆盖之前,所有的f数据都是属于上一层也就是第一层的,我们就当作i-1层数据已经准备好了,然后把max内的拆成两半分析,对于f[j]=f[j]就是不放的情况,那么总价值没有改变,所以对于f[j]=f[j]就是形式上的更新数据,把i-1层的f[j],给了i-1层的f[j]...对于f[j]=f[j-c[i]]+w[i],那个w[i]是肯定要加的不用讨论,然后我们观察一下,对于下标j-c[i]是不是肯定会小于j,那么如果说j从V~0也就是从最大到最小,每次赋值处理都是从前面的格子看看数据参考,并没有修改

再详细点说的话就是对于f[j]=f[j-c[i]]+w[i],f[j-c[i]]是第i-1层的东西,让j=v~0是为了让f数组每次滚动覆盖时都是覆盖接下来不需要用的位置,比如说处理到第f[8]位时,假如接下来的max判定后面那种方法总价值大,然后假设c[i]=3,这时后就相当与f[8]=f[8-c[i]=5]+w[i],我们这里只是参考了f[5]的数据,并没有改变它,因为说不定计算新一轮f[6]时又要用到旧的f[5]呢,可是我们刷新了f[8]的数字后,再j--,计算f[7],再j--,计算f[6],都不会再用到f[8]这个数据,这是由于f[j-c[i]] 中的减c[i]导致的,反之,假若我们让j=0~v,就可能出现新数据被新数据覆盖的结果,我们是有"底线"的,只允许新数据覆盖旧数据

对于j,如果要处理f[j]=max(f[j],f[j-c[i]]+w[i]),就得当j>=c[i]时处理,因为如果j<c[i],那么j-c[i]为负,下标负的情况没必要考虑,如果考虑了还可能会溢出

 其实对于max,还用另一个小东西代替,有没有发现,如果f[j-c[i]]+w[i]>f[j],就选f[j-c[i]]+w[i],如果f[j-c[i]]+w[i]<f[j],那选f[j]和没选一样,所以待会的空间优化版省掉了max函数,少用一种函数

#include<bits/stdc++.h>//万能头文件
#define ll long long
using namespace std;
const ll maxn=100;
ll n,v,f[maxn];
ll c[maxn];//每个物品占用空间
ll w[maxn];//每个物品的价值int main()
{cin>>n>>v;for(ll i=1;i<=n;i++)scanf("%lld",&c[i]);for(ll i=1;i<=n;i++)scanf("%lld",&w[i]);for(ll i=1;i<=n;i++)//第i个物品for(ll j=v;j>=1;j--)//剩余空间j
        {if(f[j]<=f[j-c[i]]+w[i] && j-c[i]>=0 )//二维数组变一维数组f[j]=f[j-c[i]]+w[i];//如果值得改变并且j的空间还装得下就赋新值
        }cout<<f[v]<<endl;//输出答案

}
空间优化版01背包
点击"+"号展开代码,为了排版好看把代码折叠了,为了防止有人点不开文章底部还有一份没折叠的

三.初始化的细节

初始化有两种,一种情况是只要求价值最大,另外一种是要求完全刚好塞满,第一种的初始化是赋值为0,第二种的初始化是赋值为负无穷,因为没有塞满,所以数据实际上不存在,也就是让不存在的数不现实化,让与这种数相关的数据都不可用化

下面贴一些背包九讲的文字

1.4 初始化的细节问题
我们看到的求最优解的背包问题题目中,事实上有两种不太相同的问法。 有的题目要求“恰好装满背包”时的最优解,有的题目则并没有要求必须把背 包装满。一种区别这两种问法的实现方法是在初始化的时候有所不同。
3
如果是第一种问法,要求恰好装满背包,那么在初始化时除了F[0]为0,其 它F[1..V ]均设为−∞,这样就可以保证最终得到的F[V ]是一种恰好装满背包的 最优解。 如果并没有要求必须把背包装满,而是只希望价格尽量大,初始化时应该 将F[0..V ]全部设为0。 这是为什么呢?可以这样理解:初始化的F数组事实上就是在没有任何物 品可以放入背包时的合法状态。如果要求背包恰好装满,那么此时只有容量 为0的背包可以在什么也不装且价值为0的情况下被“恰好装满”,其它容量的 背包均没有合法的解,属于未定义的状态,应该被赋值为-∞了。如果背包并非 必须被装满,那么任何容量的背包都有一个合法解“什么都不装”,这个解的 价值为0,所以初始时状态的值也就全部为0了。 这个小技巧完全可以推广到其它类型的背包问题,后面也就不再对进行状 态转移之前的初始化进行讲解。
初始化的细节问题-背包九讲

四.常数级的优化

1.5 一个常数优化
上面伪代码中的
for i = 1 to N for v = V to Ci
中第二重循环的下限可以改进。它可以被优化为
for i = 1 to N for v = V to max(V −ΣN i Wi,Ci) 这个优化之所以成立的原因请读者自己思考。(提示:使用二维的转移方程思 考较易。)
常数级优化-背包九讲

不得不说,这也太抠门了,算法效率追求到极致

 

 五.小结

01背包很重要,是后面的基础

要学会推导状态转移方程与实现它

要学会去优化空间复杂度

PS:祝每个看到这里的人都能掌握01背包

 

 

接下来放一下代码大合集

普通版代码
#include<bits/stdc++.h>//万能头文件
#define ll long long
using namespace std;
const ll maxn=100;
ll n,v,f[maxn][maxn];
ll c[maxn];//每个物品占用空间
ll w[maxn];//每个物品的价值
int main()
{cin>>n>>v;for(ll i=1;i<=n;i++)scanf("%lld",&c[i]);for(ll i=1;i<=n;i++)scanf("%lld",&w[i]);for(ll i=1;i<=n;i++)//第i个物品for(ll j=v;j>=0;j--)//剩余空间j
        {if(j >= c[i])//如果装得下f[i][j]=max( f[i-1][j-c[i]]+w[i],f[i-1][j]);else//如果装不下f[i][j]=f[i-1][j];}cout<<f[n][v]<<endl;//输出答案

}
空间优化版代码
#include<bits/stdc++.h>//万能头文件
#define ll long long
using namespace std;
const ll maxn=100;
ll n,v,f[maxn];
ll c[maxn];//每个物品占用空间
ll w[maxn];//每个物品的价值int main()
{cin>>n>>v;for(ll i=1;i<=n;i++)scanf("%lld",&c[i]);for(ll i=1;i<=n;i++)scanf("%lld",&w[i]);for(ll i=1;i<=n;i++)//第i个物品for(ll j=v;j>=1;j--)//剩余空间j
        {if(f[j]<=f[j-c[i]]+w[i] && j-c[i]>=0 )//二维数组变一维数组f[j]=f[j-c[i]]+w[i];//如果值得改变并且j的空间还装得下就赋新值
        }cout<<f[v]<<endl;//输出答案

}

 

转载于:https://www.cnblogs.com/zyacmer/p/9961710.html

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

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

相关文章

linux内核源码剖析 博客,【Linux内存源码分析】页面迁移

页面迁移其实是伙伴管理算法中的一部分&#xff0c;鉴于其特殊性&#xff0c;特地另行分析。它是2007年的时候&#xff0c;2.6.24内核版本开发时&#xff0c;新增碎片减少策略(the fragmentation reduction strategy)所引入的。该策略也称之为反碎片技术(anti-gragmentation)。…

2018年下半年网络公式考试案例分析真题

阅读以下说明&#xff0c;回答问题1至问题3&#xff0c;将解答填入答题纸对应的解答栏内。【说明】某公司网络划分为两个子网&#xff0c;其中设备A是DHCP服务器&#xff0c;如图3-1所示。 【问题1】(6分&#xff0c;每空2分)DHCP在分配IP地址时使用 (1) 的方式&#xff0c; 而…

【单片机入门】(三)应用层软件开发的单片机学习之路-----UART串口通讯和c#交互...

本文由网友投稿。作者&#xff1a;陈显达原文标题&#xff1a;【单片机入门】(三)应用层软件开发的单片机学习之路-----UART串口通讯和c#交互原文链接&#xff1a;https://www.cnblogs.com/1996-Chinese-Chen/p/16826558.html引言在第一章博客中&#xff0c;我们讲了Arduino对E…

机器学习案例丨基于广泛和深入的推荐 - 餐厅评级预测

点击上方蓝字关注我们&#xff08;本文阅读时间&#xff1a;18分钟&#xff09;Microsoft Azure Machine Learning Studio 是微软强大的机器学习平台&#xff0c;在设计器中&#xff0c;微软内置了15个场景案例&#xff0c;但网上似乎没有对这15个案例深度刨析的分析资料&#…

css linux 等宽字体,比例字体等宽字体

我们都知道等宽字体和比例字体的区别&#xff0c;就在于比例字体(Monospaced Font)即每个字母宽度是按一定比例自动调整的&#xff0c;而等宽字体(Proportional font)则是固定宽度&#xff0c;固定间距&#xff0c;字体的每一个字母和字符所占的水平空间都是相同的。比例字体&a…

三星智能家居系统频繁故障 大批用户受到影响

Shelley Powers正在她密苏里州郊区的房子中鼾睡&#xff0c;突然警铃大作将她惊醒&#xff0c;在仔细排查之后&#xff0c;发现是虚惊一场&#xff0c;是安全传感器误报。在此之后的几周&#xff0c;她的三星Smart Things智能家居系统频繁出现故障&#xff0c;比如设定的灯具不…

适用于 .NET 的开源文本差异对比组件

你好&#xff0c;这里是 Dotnet 工具箱&#xff0c;定期分享 Dotnet 有趣&#xff0c;实用的工具或组件&#xff0c;希望对您有用&#xff01;简介对于开发人员来说&#xff0c;Git 是我们经常使用的工具&#xff0c;在每次编写完代码并提交后&#xff0c;我们可以通过 git dif…

box-shadow阴影合集

2019独角兽企业重金招聘Python工程师标准>>> * box-shadow可以设置6个值。其中4个可选&#xff1b;2个必须指定&#xff1a;分别是x轴偏移量和y轴偏移量&#xff0c;这2个值可以是正值&#xff0c;可以是负值&#xff0c;也可以是0&#xff0c;但不可以省略不写 阴影…

《数据科学R语言实践:面向计算推理与问题求解的案例研究法》一一2.5 为跨年度的个人参赛选手构造记录...

本节书摘来自华章计算机《数据科学R语言实践&#xff1a;面向计算推理与问题求解的案例研究法》一书中的第2章&#xff0c;第2.5节,作者&#xff1a;[美] 德博拉诺兰&#xff08;Deborah Nolan&#xff09;  邓肯坦普朗&#xff08;Duncan Temple Lang&#xff09;  更多章…

基于 abp 微服务架构的开源低代码平台

你好&#xff0c;这里是 Dotnet 工具箱&#xff0c;定期分享 Dotnet 有趣&#xff0c;实用的工具或组件&#xff0c;希望对您有用&#xff01;简介 ABP-MicroService是 一个基于ABP vNext微服务架构、vue-element-admin的后台管理框架&#xff0c;适用于大型分布式业务系统和企…

linux android sdk gengxinman,Android 实现增量更新

一、概述增量更新相较于全量更新的好处不言而喻&#xff0c;利用差分算法获得1.0版本到2.0版本的差分包&#xff0c;这样在安装了1.0的设备上只要下载这个差分包就能够完成由1.0-2.0的更新。比如&#xff1a;存在一个1.0版本的apkapk1.png然后需要升级到2.0版本&#xff0c;而2…

Pycharm上Django的使用 Day8

2019独角兽企业重金招聘Python工程师标准>>> 1.添加新条目 1>编写用于添加新条目的表单 在forms.py中创建一个与模型Entry相关联的表单 1处给字段text指定一个空标签 2处定义小部件widgets,widgets是一个HTML表单元素 2>定义new_entry的URL模式 在用于添加新条…

物联网创新领域的三大驱动性趋势

从设备对接到区块链&#xff0c;今天我们将介绍几项物联网开发者必须了解的最新技术趋势。 物联网正在快速改变着我们的工作与生活方式。过去十年以来&#xff0c;利用惟一IP地址实现联网早已不是难事&#xff0c;但传感器、处理器以及其它多种新兴技术的涌现真正让物联网走向了…

TensorFlow.NET 实战 -- 为.NET开发者开启机器学习之旅

在IT技术飞速发展的今天&#xff0c;云计算、人工智能、大数据和云原生应用等新兴技术的发展为我们带来一波又一波的浪潮&#xff0c;也对软件开发者提出了更高的要求&#xff0c;特别是人工智能开发领域&#xff0c;应运而生的新概念让人目不暇接。作为一个.NET开发者的你&…

Arduino I/O Expansion Shield V7.1

介绍&#xff1a; 传感器扩展板&#xff0c;目的是为初学者省去繁琐的面包板接线和故障排除&#xff0c;专注实现自己的创意想法。 扩展板V7.1不仅保留老版本的所有优点&#xff0c;改善了它的易用性&#xff0c;更重要的是它兼容Arduino新一代主控器DUE。现在V7.1以其强大的扩…

简单的单臂路由的配置实验 (思科)

实验名称&#xff1a;简单的单臂路由的 配置实验 &#xff08;思科&#xff09;实验拓扑&#xff1a; 实验需要&#xff1a;1、按图中所示配置设网络备vlan&#xff0c;IP地址2、能够使各pc互相ping通。 实验步骤&#xff1a; 1、配置终端设备&#xff1a;pc1-pc5按照拓扑图中所…

【Redis源码分析】Redis命令处理生命周期

运营研发团队 李乐 前言 本文主要讲解服务器处理客户端命令请求的整个流程&#xff0c;包括服务器启动监听&#xff0c;接收命令请求并解析&#xff0c;执行命令请求&#xff0c;返回命令回复等&#xff0c;这也是本文的主题“命令处理的生命周期”。 Redis服务器作为典型的事件…

GDB调试qemu-kvm

GDB调试qemu-kvm 前面几篇博文都是记录一些kvm相关包编译安装及使用&#xff0c;但都没深入去代码看看。看源码在配合上相关原理才能更好的理解kvm。但qemu-kvm的代码量很多&#xff0c;对我来讲直接看源码收获甚少&#xff0c;所以找了个调试工具——GDB来配合阅读代码。接下来…

用.NET设计一个假装黑客的屏幕保护程序

本文主要介绍屏幕保护程序的一些相关知识&#xff0c;以及其在安全方面的用途&#xff0c;同时介绍了如何使用 .NET 开发一款屏幕保护程序&#xff0c;并对核心功能做了介绍&#xff0c;案例代码开源&#xff1a;https://github.com/sangyuxiaowu/HackerScreenSaver背景前几天在…

【IntelliJ】IntelliJ IDEA常用设置及快捷键以及自定义Live templates

IntelliJ IDEA是一款非常优秀的JAVA编辑器&#xff0c;初学都可会对其中的一些做法感到很别扭&#xff0c;刚开始用的时候我也感到很不习惯&#xff0c;在参考了网上一些文章后在这里把我的一些经验写出来&#xff0c;希望初学者能快速适应它&#xff0c;不久你就会感觉到编程是…