`
blue2048
  • 浏览: 185979 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

讲透完全背包算法

阅读更多

 

1、问题描述

 

已知:有一个容量为V的背包和N件物品,第i件物品的重量是weight[i],收益是cost[i]。

条件:每种物品都有无限件,能放多少就放多少。

问题:在不超过背包容量的情况下,最多能获得多少价值或收益

举例:物品个数N = 3,背包容量为V = 5,则背包可以装下的最大价值为40.

----------------------------------------------

2、基本思路(直接扩展01背包的方程)

由于本问题类似于01背包问题,在01背包问题中,物品要么取,要么不取,而在完全背包中,物品可以取0件、取1件、取2件...直到背包放不下位置。因此,可以直接在01背包的递推式中扩展得到。

 

[cpp] view plaincopy
 
  1. f[i][v]:表示前i件物品放入容量为v的容量中时的最大收益  
  2. 递推式:  
  3. f[i][v] = max(f[i - 1][v],f[i - K * weight[i]] + K * Value[i]); 其中  1 <= K * weight[i] <= v,(v指此时背包容量)  
  4. //初始条件  
  5. f[0][v] = 0;  
  6. f[i][0] = 0;  

代码:

 

 

[cpp] view plaincopy
 
  1. #include <iostream>  
  2. #include <assert.h>  
  3. using namespace std;  
  4. /* 
  5. f[i][v]:前i件物品放入背包容量为v的背包获得的最大收益 
  6.  
  7. f[i][v] = max(f[i - 1][v],f[i - 1][v - k * Wi] + k * Vi,其中 1<=k<= v/Wi) 
  8.  
  9. 边界条件 
  10. f[0][v] = 0; 
  11. f[i][0] = 0; 
  12. */  
  13.   
  14. const int N = 3;  
  15. const int V = 5;  
  16. int weight[N + 1] = {0,3,2,2};  
  17. int Value[N + 1] = {0,5,10,20};  
  18.   
  19. int f[N + 1][V + 1] = {0};  
  20.   
  21. int Completeknapsack()  
  22. {  
  23.     //边界条件  
  24.     for (int i = 0;i <= N;i++)  
  25.     {  
  26.         f[i][0] = 0;  
  27.     }  
  28.     for (int v = 0;v <= V;v++)  
  29.     {  
  30.         f[0][v] = 0;  
  31.     }  
  32.     //递推  
  33.     for (int i = 1;i <= N;i++)  
  34.     {  
  35.         for (int v = 1;v <= V;v++)  
  36.         {  
  37.             f[i][v] = 0;  
  38.             int nCount = v / weight[i];  
  39.             for (int k = 0;k <= nCount;k++)  
  40.             {  
  41.                 f[i][v] = max(f[i][v],f[i - 1][v - k * weight[i]] + k * Value[i]);  
  42.             }  
  43.         }  
  44.     }  
  45.     return f[N][V];  
  46. }  
  47.   
  48. int main()  
  49. {  
  50.     cout<<Completeknapsack()<<endl;  
  51.     system("pause");  
  52.     return 1;  
  53. }  

复杂度分析:

程序需要求解N*V个状态,每一个状态需要的时间为O(v/Weight[i]),总的复杂度为O(NV*Σ(V/c[i]))。

代码优化:

完全背包问题有一个很简单有效的优化,是这样的:若两件物品i、j满足c[i]<=c[j]且w[i]>=w[j],则将物品j去掉,不用考虑。

即,如果一个物品A是占的地少且价值高,而物品B是占地多,但是价值不怎么高,那么肯定是优先考虑A物品的。

这里代码略。

----------------------------------------------

3、转换为01背包问题求解(直接利用01背包)

思路 1、完全背包的物品可以取无限件,根据背包的总容量V和第i件物品的总重量Weight[i],可知,背包中最多装入V/Weight[i](向下取整)件该物品。因此可以直接改变第i件物品的总个数,使之达到V/Weight[i](向下取整)件,之后直接利用01背包的思路进行操作即可。

举例:物品个数N = 3,背包容量为V = 5。

拆分之前的物品序列:


拆分之后的物品序列:


根据上述思想:在背包的最大容量(5)中,最多可以装入1件物品一,因此不用扩展物品一。最多可以装入2件物品二,因此可以扩展一件物品二。同理,可以扩展一件物品三。

时间复杂度的分析:O(NNew*V),其中V表示扩展前背包容量,NNew表示扩展后物品的个数,NNew =Σ(V/Weight[i](向下取整))

思路 2、对物品进行拆分时,拆成二进制的形式。

具体思路:把第i种物品拆成费用为weight[i]*2^k、价值为w[i]*2^k的若干件物品,其中k满足weight[i]*2^k<=V。

思路:这是二进制的思想,因为不管最优策略选几件第i种物品,总可以表示成若干个2^k件物品的和。

这样把每种物品拆成O(log V/weight[i])件物品,是一个很大的改进。

举例:物品个数N = 3,背包总容量为V = 5。
拆分之前的物品序列:


拆分之后的物品序列:


为了和前面的例子保持一致,这里才用之前的例子,但是这个例子没有更好的说明二进制的拆分方法拆分的物品个数会少写。

假设物品A的重量为2,收益为3,背包的总重量为20。

根据第一种拆分,可以拆成10个物品,每一个物品的重量为2,收益为3。

根据第二种拆分方法,可以拆成4个物品,分别是物品一(重量为1*2,收益为3),物品二(重量为2*2,收益为6),物品三(重量为4*2,收益为12),物品四(重量为8*2,收益为24)。

时间复杂度的分析:O(NNEW*V),其中V表示扩展前背包容量,NNew表示扩展后物品的个数,NNew = Σ(log V/weight[i](向下取整))

代码:

 

[cpp] view plaincopy
 
  1. #include <iostream>  
  2. #include <vector>  
  3. #include <assert.h>  
  4. using namespace std;  
  5. /* 
  6. f[v]:表示第i件物品放入容量为v的背包后,获得的最大容量 
  7. f[v] = max(f[v],f[v - weight[i]] + value[i]); 
  8. 初始条件:f[0] = 0; 
  9. */  
  10.   
  11. const int N = 3;  
  12. const int V = 20;//5  
  13. int weight[N + 1] = {0,3,2,2};  
  14. int Value[N + 1] = {0,5,10,20};  
  15.   
  16. int NNew = 0;  
  17. vector<int> weightVector;  
  18. vector<int> Valuevector;  
  19. int f[V + 1] = {0};  
  20. /*拆分物品*/  
  21. void SplitItem()  
  22. {  
  23.     //从1开始  
  24.     weightVector.push_back(0);  
  25.     Valuevector.push_back(0);  
  26.     //开始拆分  
  27.     int nPower = 1;  
  28.     for (int i = 1;i <= N;i++)  
  29.     {  
  30.         nPower = 1;  
  31.         while (nPower * weight[i] <= V)  
  32.         {  
  33.             weightVector.push_back(nPower * weight[i]);  
  34.             Valuevector.push_back(nPower * Value[i]);  
  35.             nPower <<= 1;  
  36.         }  
  37.     }  
  38. }  
  39.   
  40. int Completeknapsack()  
  41. {  
  42.     //拆分物品  
  43.     SplitItem();  
  44.     //转化为01背包处理  
  45.     NNew = weightVector.size() - 1;//多加了一个0,要减去  
  46.   
  47.     for (int i = 1;i <= NNew;i++)//物品个数变化  
  48.     {  
  49.         for (int v = V;v >= weightVector[i];v--)//背包容量仍是V  
  50.         {  
  51.             f[v] = max(f[v],f[v - weightVector[i]] + Valuevector[i]);  
  52.         }  
  53.     }  
  54.   
  55.     return f[NNew];  
  56. }  
  57. int main()  
  58. {  
  59.     cout<<Completeknapsack()<<endl;  
  60.     system("pause");  
  61.     return 1;  
  62. }  

4、O(VN)的算法

 

伪代码

 

[cpp] view plaincopy
 
  1. for (int i = 1;i <= N;i++)  
  2. {  
  3.     for (int v = weight[i];v <= V;v++)  
  4.     {  
  5.         f[v] = max(f[v],f[v - weight[i]] + Value[i]);  
  6.     }  
  7. }  

分析:这和01背包的伪代码很相似,在01背包的代码中,v变化的区间是逆序循环的,即[V,Weight[i]]。而这里,v变化的区间是顺序循环的,即为[Weight[i],V]。

 

原因:

再次给出定义:

f[i][v]表示把前i件物品放入容量为v的背包时的最大代价。

f[i-1][v-c[i]]表示把前i - 1件物品放入容量为v的背包时的最大代价.

在01背包中,v变化的区间是逆序循环的原因:要保证由状态f[i-1][v-c[i]]递推状态f[i][v]时,f[i-1][v-c[i]]没有放入第i件物品。之后,在第i循环时,放入一件第i件物品。

01背包的方程:

[cpp] view plaincopy
 
  1. f[i][v] = max(f[i - 1][v],f[i - 1][v - weight[i]] + Value[i])    

 

在完全背包中,v变化的区间是顺序循环的原因完全背包的特点是每种物品可选无限件,在求解加选第i种物品带来的收益f[i][v]时,在状态f[i][v-c[i]]中已经尽可能多的放入物品i了,此时在f[i][v-c[i]]的基础上,我们可以再次放入一件物品i,此时也是在不超过背包容量的基础下,尽可能多的放入物品i。

完全背包的方程:

 

[cpp] view plaincopy
 
  1. f[i][v] = max(f[i - 1][v],f[i][v - weight[i]] + Value[i]);  

举例:

物品个数N = 3,背包总容量为V = 5。

物品信息:


完全背包:


分析:

i = 2,表示正在处理第2件物品。在求解f[2][4]时,如果要计算把第2件物品放入背包后的代价时,我们需要知道f[2][2],此时f[2][2]中已经尽全力放入第2件物品了(已经放入一件)。此时此刻还可以在放入一件第2件物品,在背包容量为4时,最多可以放入两件第二件物品。

总结下,f[i][v]:表示在背包容量为v时,尽全力的放入第i件物品的代价。f[i][v - weight[i]]:表示在背包容量为v - weight[i]时,尽全力的放入第i件物品的代价。因此由f[i][v - weight[i]]转换为f[i][v]时,也是在f[i][v - weight[i]]的基础上有加入了一件物品i。

为了节省保存状态的空间,可以直接使用一维数组保存状态。

代码:迭代方程:f[i][v] = max(f[i - 1][v],f[i][v - weight[i]] + Value[i]);

 

[cpp] view plaincopy
 
  1. #include <iostream>  
  2. #include <vector>  
  3. #include <assert.h>  
  4. using namespace std;  
  5. const int N = 3;  
  6. const int V = 5;//5  
  7. int weight[N + 1] = {0,3,2,2};  
  8. int Value[N + 1] = {0,5,10,20};  
  9.   
  10. int f[N + 1][V + 1] = {0};  
  11.   
  12. int Completeknapsack()  
  13. {  
  14.     //初始化  
  15.     for (int i = 0;i <= N;i++)  
  16.     {  
  17.         f[i][0] = 0;  
  18.     }  
  19.     for (int v = 0;v <= V;v++)  
  20.     {  
  21.         f[0][v] = 0;  
  22.     }  
  23.     for (int i = 1;i <= N;i++)  
  24.     {  
  25.         for (int v = weight[i];v <= V;v++)  
  26.         {  
  27.             f[i][v] = max(f[i - 1][v],f[i][v - weight[i]] + Value[i]);  
  28.         }  
  29.     }  
  30.     return f[N][V];  
  31. }  
  32.   
  33. int main()  
  34. {  
  35.     cout<<Completeknapsack()<<endl;  
  36.     system("pause");  
  37.     return 1;  
  38. }  

代码:迭代方程:f[v] = max(f[v],f[v - weight[i]] + Value[i]);

 

 

[cpp] view plaincopy
 
  1. #include <iostream>  
  2. using namespace std;  
  3. const int N = 3;  
  4. const int V = 5;//5  
  5. int weight[N + 1] = {0,3,2,2};  
  6. int Value[N + 1] = {0,5,10,20};  
  7.   
  8. int f[V + 1] = {0};  
  9.   
  10. int Completeknapsack()  
  11. {  
  12.     f[0] = 0;  
  13.     for (int i = 1;i <= N;i++)  
  14.     {  
  15.         for (int v = weight[i];v <= V;v++)  
  16.         {  
  17.             f[v] = max(f[v],f[v - weight[i]] + Value[i]);  
  18.         }  
  19.     }  
  20.     return f[V];  
  21. }  
  22. int main()  
  23. {  
  24.     cout<<Completeknapsack()<<endl;  
  25.     system("pause");  
  26.     return 1;  
  27. }  

转自 http://blog.csdn.net/insistgogo/article/details/11081025

 

分享到:
评论

相关推荐

    背包问题9讲

    2. 完全背包问题:在完全背包问题中,每种物品可以使用无限次,这使得问题的求解方法与01背包问题不同。完全背包问题可以采用贪心策略来解决,也可以用动态规划的方法来求解更复杂的情况。 3. 多重背包问题:多重...

    基于A*算法的往返式全覆盖路径规划改进及其Matlab实现

    内容概要:本文详细介绍了如何利用A*算法改进传统的往返式路径规划,解决扫地机器人在复杂环境中容易卡住的问题。首先构建了一个可视化的栅格地图用于模拟环境,然后引入了优先级运动规则,使机器人能够有规律地进行往返清扫。当遇到死角时,通过A*算法计算最佳逃生路径,确保机器人能够顺利脱困并继续完成清扫任务。实验结果显示,改进后的算法显著提高了清洁覆盖率,降低了路径重复率。此外,还讨论了一些潜在的优化方向,如动态调整启发函数权重、断点续传以及能耗模型等。 适合人群:对路径规划算法感兴趣的科研人员、自动化专业学生、扫地机器人开发者。 使用场景及目标:适用于需要高覆盖率和低重复率的室内清洁任务,旨在提高扫地机器人的工作效率和智能化水平。 其他说明:文中提供了详细的Matlab代码实现,并附带了仿真测试结果,有助于读者理解和复现该算法。

    爬取喜马拉雅听书(1).py

    爬取喜马拉雅听书(1)

    安卓向上传递数据学习笔记总结

    安卓向上传递数据学习笔记总结

    tigervnc-selinux-1.11.0-9.el8.x64-86.rpm.tar.gz

    1、文件说明: Centos8操作系统tigervnc-selinux-1.11.0-9.el8.rpm以及相关依赖,全打包为一个tar.gz压缩包 2、安装指令: #Step1、解压 tar -zxvf tigervnc-selinux-1.11.0-9.el8.tar.gz #Step2、进入解压后的目录,执行安装 sudo rpm -ivh *.rpm

    户外储能电源双向逆变器板生产资料及技术规格详解

    内容概要:本文详细介绍了户外储能电源双向逆变器板的技术资料及其特点。涵盖原理文件、PCB文件、源代码、电感与变压器规格参数等,适用于2KW(最大3KW)的户外储能电源。文中强调了双向软开关DC-DC设计、两颗M0+ 32位MCU的分工、SPWM调制方式、H桥IGBT的应用、详细的电气参数和技术特性。此外,还包括了SPWM信号生成代码示例、硬件设计细节、生产注意事项等。 适合人群:从事户外储能电源开发的技术人员、电子工程师、产品经理等。 使用场景及目标:帮助开发者快速掌握双向逆变器板的设计和生产要点,缩短产品研发周期,提高产品质量和可靠性。具体应用场景包括但不限于户外应急电源、便携式储能设备等。 其他说明:本文提供了丰富的技术细节和实践经验,如双向软开关DC-DC设计、SPWM调制、IGBT驱动、EMC整改记录等,有助于解决实际开发中的难题。同时,附带的实际案例展示了该方案的成功应用,进一步证明了其可行性和优越性。

    电能质量分析:间谐波分析.zip

    电子仿真教程,从基础到精通,每个压缩包15篇教程,每篇教程5000字以上。

    【计算机科学领域】美国计算机学会(ACM):组织架构、使命愿景、核心价值及活动项目介绍

    内容概要:美国计算机学会(ACM)是一个成立于1947年的国际性计算机专业组织,致力于推动计算机科学的发展,提供教育、资源和专业发展机会。ACM的使命是促进计算机科学和信息技术领域的进步,愿景是成为全球计算机专业人士的首选组织。其核心价值包括卓越、诚信、包容性、合作和创新。ACM定期举办学术会议,如SIGGRAPH和图灵奖颁奖典礼,出版高质量的学术期刊和会议论文集,涵盖人工智能、软件工程、网络安全等领域。此外,ACM还提供在线课程、研讨会、认证项目等教育资源,以及职业规划、网络机会和领导力培训等职业发展服务。ACM图灵奖被誉为“计算机界的诺贝尔奖”,每年颁发给对计算机科学和技术做出重大贡献的个人。; 适合人群:计算机科学领域的专业人士、教育工作者、工程师和学生。; 使用场景及目标:①了解计算机科学领域的最新研究成果和发展趋势;②获取高质量的教育资源和职业发展机会;③参与计算机科学领域的学术交流和合作。; 其他说明:ACM作为一个全球性的组织,在教育、研究和行业实践中发挥着重要作用,推动了技术创新和社会进步。

    最新版logstash-8.17.4-windows-x86-64.zip

    logstash-8.17.4-windows-x86_64.zip

    一个基于Springboot使用Aspect实现一个切面,以记录日志为例

    springboot 一个基于Springboot使用Aspect实现一个切面,以记录日志为例

    音箱底部折边设备sw22可编辑_三维3D设计图纸_包括零件图_机械3D图可修改打包下载_三维3D设计图纸_包括零件图_机械3D图可修改打包下载.zip

    音箱底部折边设备sw22可编辑_三维3D设计图纸_包括零件图_机械3D图可修改打包下载_三维3D设计图纸_包括零件图_机械3D图可修改打包下载.zip

    基于Python Django MySQL的个性化图书推荐系统:协同过滤算法及远程部署实现

    内容概要:本文详细介绍了如何使用Python、Django和MySQL构建一个完整的个性化图书推荐系统。系统从前端界面设计、后端逻辑实现到数据库设计,涵盖了用户管理、图书管理、评分系统等功能模块。重点讲解了基于用户和项目的协同过滤算法实现,以及在用户评分数据不足时的标签推荐备份方案。此外,还包括了系统部署、测试和优化的具体步骤,如云服务器部署、性能测试、数据库优化等。 适合人群:具备一定Python和Web开发基础的研发人员,尤其是对推荐系统感兴趣的技术爱好者。 使用场景及目标:适用于希望深入了解图书推荐系统的工作原理和实现细节的技术人员。目标是帮助读者掌握从零开始搭建一个完整的个性化推荐系统的方法,包括前后端开发、算法实现和系统部署。 其他说明:文中提供了大量代码示例和实战经验,如数据库设计、爬虫实现、权限管理等,有助于读者更好地理解和应用相关技术。

    Ai和python学习资料

    Ai和python学习资料

    文本摘要.py

    文本摘要

    冲击试验机sw22_三维3D设计图纸_包括零件图_机械3D图可修改打包下载_三维3D设计图纸_包括零件图_机械3D图可修改打包下载.zip

    冲击试验机sw22_三维3D设计图纸_包括零件图_机械3D图可修改打包下载_三维3D设计图纸_包括零件图_机械3D图可修改打包下载.zip

    Java开发MybatisPlus框架详解:增强Mybatis功能实现高效CRUD操作与代码生成

    内容概要:本文详细介绍了MyBatis Plus(MP),它是MyBatis的增强工具,旨在简化CRUD操作、提高开发效率。其主要功能包括内置分页插件、简化CRUD操作以及代码生成器。使用时只需引入相应依赖,自定义Mapper接口继承BaseMapper泛型接口,并通过实体类反射获取数据库表信息。文章还介绍了常用注解如@TableName、@TableId、@TableField、@TableLogic和@Version,配置项如全局配置、类型别名和Mapper文件路径,以及核心功能如批量插入、分页查询、条件构造器(Wrapper)等。此外,扩展功能涵盖逻辑删除、枚举处理器和JSON处理器,插件功能则包括分页插件的配置和使用。 适合人群:具备一定Java开发经验,尤其是熟悉MyBatis框架的开发者,特别是那些希望提高开发效率、减少重复代码的工作1-3年研发人员。 使用场景及目标:①简化数据库操作,提高开发效率;②快速生成代码,减少手动编写SQL语句的工作量;③实现分页查询、逻辑删除、枚举和JSON字段处理等高级功能,提升应用的灵活性和可维护性。 其他说明:本文不仅提供了MyBatis Plus的功能介绍和使用方法,还深入探讨了条件构造器(Wrapper)的使用技巧,帮助开发者更好地理解和掌握这一强大的工具。在实际开发中,合理利用这些功能可以显著提高开发效率和代码质量。建议在学习过程中结合具体项目实践,逐步掌握各个功能的应用场景和最佳实践。

    电路仿真:射频电路仿真.zip

    电子仿真教程,从基础到精通,每个压缩包15篇教程,每篇教程5000字以上。

    【java毕业设计】Springboot+Vue高考志愿填报系统 源码+sql脚本+论文 完整版

    这个是完整源码 SpringBoot + vue 实现 【java毕业设计】Springboot+Vue高考志愿填报系统 源码+sql脚本+论文 完整版 数据库是mysql 随着高考制度的不断完善和高等教育资源的日益丰富,高考志愿填报成为考生和家长关注的焦点。本文旨在开发一个基于Spring Boot后端框架、Vue.js前端框架和实现以下功能:考生信息管理、院校信息查询、专业信息查询、志愿填报、志愿评测等。通过Spring Boot框架构建后端服务,提供 API接口与前端进行交互;Vue.js框架用于构建前端用户界面,实现数据的动态展示和交互操作;MySQL数据库用于存储考生信息、院校信息、专业信息等数据。 在系统设计过程中,我们充分考MySQL数据库的高考志愿填报系统,提高志愿填报的效率和准确性,为考生和家长提供便捷的服务。 系统主要实现以下功能:考分考MySQL数据库的高考志愿填报系统,提高志愿填报的效率和准确性,为考生和家长提供便捷的服务生信息管理、院校信息查询、专业信息查询、志愿填报、志愿评测等。通过Spring Boot框架构建后端服务,提供 API接口与前端进行交互;Vue.js框架用于构建前端用户界面,实现数据的动态展示和交互操作;MySQL数据库用于存储考生信息、院校信息、专业信息等数据。 在系统设计过程中,我们充分考虑了系统的易用性、可扩展性和安全性。通过合理的数据库设计和优化,提高了系统的查询效率。同时,采用Spring Security等安全框架对系统进行安全防护,确保数据的安全性。 本文详细阐述了系统的需求分析、设计、实现和测试过程,并对关键技术和实现难点进行了深入探讨。通过实验验证,本系统能够满足高考志愿填报的基本需求,为考生和家长提供了高效、便捷的服务。此外,本文还对系统未来的发展方向和改进空间进行了展望,以期进一步完善系统功能,提高用户体验。

    基于MATLAB的特征选择算法:SBS与SFS的实现及其应用场景

    内容概要:本文详细介绍了基于MATLAB实现的两种经典特征选择算法——向后搜索(SBS)和向前搜索(SFS)。首先通过构造简单的虚拟数据集展示了这两个算法的基本思想和实现步骤。接着深入探讨了SBS和SFS的具体实现方式,包括特征集的初始化、特征的选择/剔除机制以及评价函数的设计。文中还提供了具体的MATLAB代码示例,帮助读者更好地理解和应用这两种算法。此外,文章讨论了SBS和SFS的特点和局限性,并给出了在实际工程项目中的选型建议。 适合人群:对特征选择有一定兴趣并希望深入了解SBS和SFS算法的初学者,尤其是那些希望通过MATLAB进行特征选择研究的人群。 使用场景及目标:适用于需要从大量特征中挑选出最具影响力的少数特征的情况,如生物医学数据分析、图像识别等领域。主要目标是提高模型性能的同时减少计算成本。 其他说明:尽管SBS和SFS属于较为基础的特征选择方法,在现代工业级项目中已被更先进的算法所替代,但对于理解特征选择的基本原理仍然非常重要。同时,文章强调了评价函数设计的重要性,并指出在实际应用中应综合考虑业务背景和技术因素。

Global site tag (gtag.js) - Google Analytics