1. 完全背包问题的形式化描述

完全背包问题是一类经典的DP(Dynamic Programming,动态规划)问题,问题描述如下:

有n种重量和价值分别为wi,vi的物品,从这些物品中挑选出总重量不超过W的物品,求所有挑选方案中价值vi总和的最大值。值得注意的是,每种物品都可以选取无限次。

假设第i种物品选择了ki个,ki∈N,则

目标函数为:

max{Σkivi}

约束条件为:

$ \sum k_i w_i \leq W $

2. 完全背包问题的平凡解法

首先对所有物品种类进行1~n的编号。

设函数F(i,j),其意义为:从编号1~i的种类中选取物品,装入最大允许重量为j的背包中,所能获得的最大总价值是F(i,j)。

首先考虑基准情况:选0个物品,即i=0,这时:

F(i,j) = F(0,j) = 0

对于子问题F(i,j)的求解,其最优解的决策依赖于涉及1~(i-1)类物品的子问题F(i-1,j)。

当前要做出决策,需选k个i类物品(k可以为0),根据物品的重量分为如下两种情况:

1。若物品重量wi不超过背包的允许重量j,则可分别选0个、...、k个该物品,kwi都不超过允许重量j。最后再从所有选择结果中挑选最大总价值,作为当前最优解。

F(i,j) = max{ F(i-1,j - k × wi) + k × vi| (k>=0,kwi<=j)}

2。若物品重量wi超过背包的允许重量j,则无法放入当前物品:

F(i,j) = F(i-1,j)

上述两个递推式揭示了当前状态与上一状态之间的转移关系。加上基准情况,我们发现算法已经封闭了,可以编程实现。

核心程序

输入:物品种类数n,背包最大允许重量t,第i种物品重量w[i]、价值v[i]。

输出:最大价值dp[n][t]。

初始化:清零dp数组

for(int i=1;i<=n;++i) {for(int j=0;j<=t;++j) {if(j

dp[i][j]=dp[i-1][j];

}else{for(int k=0;k*w[i]<=j;++k) {

dp[i][j]=max(dp[i][j], max(dp[i-1][j], dp[i-1][j-k*w[i]]+k*v[i]));

}

}

}

}

可以看出渐近复杂度O(n×t×w),为三次复杂度,因此该程序能解决的问题规模非常有限。但正如标题所说的,平凡解法还有较大可优化的空间。

3. 利用数据相关性——时间复杂度优化

利用循环间数据相关性优化算法是一种常见的技巧。例如“最大子段和”问题的蛮力解法中,利用数据相关性可以将O(n^3)的复杂度直接优化为O(n^2)。同理,在求Σnx=1 x!(连续阶乘和)问题中,利用数据相关性可将O(n^2)直接优化为O(n)。这些例子展现了该技巧的生命力。

回到我们的DP解法,观察for(j)与for(k)循环,猜测两个循环间存在数据相关性,例如:F(i-1,j)中选择k个物品的情况,与F(i-1,j-wi)中选择k-1个情况完全相同,推测F(i-1,j)的递推中k>=1的部分在计算F(i-1,j-wi)时就已经求出了,这意味着k循环除了第一趟执行对结果有贡献外,后续执行只是在重复之前已经完成的计算,有希望将k循环合并到j循环中。

为了验证这种想法,推导如下:

题设:

F(i,j) = max{F(i-1,j - k × wi) + k × vi|(k>=0)}  ……①

考虑当k==0时,F(i-1,j - k × wi) + k × vi退化为F(i-1,j),所以

F(i,j) = max(F(i-1,j), max{F(i-1,j - k × wi) + k × vi|(k>=1)} )

为了验证之前的猜测,将k代换为k+1。问题等价变形到k>=0的情况:

F(i,j) = max(F(i-1,j), max{F(i-1,j - (k+1) × wi) + (k+1) × vi|(k>=0)})

= max(F(i-1,j), max{F(i-1,(j - wi)- k × wi) + k × vi+ vi|(k>=0)} )    ……②

结论仍不明显,将上式②下划线部分提到max{}后面,发现:

F(i,j) = max(F(i-1,j), max{F(i-1,(j - wi)- k × wi) + k × vi|(k>=0)} +vi)

对比①知,上式蓝色部分恰等价于F(i, j - wi),这基本验证了我们的想法。

F(i,j) = max(F(i-1,j), F(i, j - wi) + vi)

这便是最终的表达式,可以看到成功消去了k。

根据推导的状态转移方程编写程序,时间复杂度直接从三次降至了二次。

需要注意,因为F(i-1,j)依赖于F(i-1,j-wi)的计算结果,所以j必须递增枚举,才能保证构造的正确性。

for(int i=1;i<=n;++i) {for(int j=0;j<=t;++j) {if(j

dp[i][j]=dp[i-1][j];

}else{

dp[i][j]=max(dp[i-1][j], dp[i][j-w[i]]+v[i]);

}

}

}

4. 降低dp数组维度——空间复杂度优化

优化前,dp数组的空间复杂度是O(n×t)的。

注意到dp数组中,dp[i]行的各元素值只依赖于前一行dp[i-1],而不依赖于dp[i-2]、……、dp[0]行。这启发我们只存储一行dp数组,然后在该行“原地”更新数据。

仍然有两种情况:

1。对于j

2。对于其它非1。的情况,需要更新dp行,dp[j]=max(dp[j], dp[j-w[i]]+v[i]); 加粗部分反映了“原地”更新策略。

for(int i=1;i<=n;++i) {for(int j=w[i];j<=t;++j) {

dp[j]=max(dp[j],dp[j-w[i]]+v[i]);

}

}

因为情况1。的存在,j的取值必须以w[i]为区间起点。

值得注意的是,j循环按递增顺序枚举,这与我们在第3节中得出的结论一致。

经过优化,空间复杂度降至了O(n))。

5. 结论

首先描述了完全背包问题,然后分析了最直观的trivial解法,由平凡解法发现循环间的数据相关性,推导出优化后的状态转移方程,推导结论使算法时间复杂度降至O(n×t)。接下来从另一角度——空间复杂度分析dp数组降维优化,使空间复杂度降低至O(n)。综合时空两个方面,得出了DP求解此类问题的优化方法。

c语言dp算法解决背包问题,DP求解完全背包问题及其优化原理相关推荐

  1. c语言dp算法,C++动态规划dp算法题

    问题1:找硬币,换钱的方法 输入: penny数组代表所有货币的面值,正数不重复 aim小于等于1000,代表要找的钱 输出: 换钱的方法总数 解法1:经典dp,空间复杂度O(n*aim) class ...

  2. AIS数据压缩-改进的DP算法(Improved DP algorithm)

    在上两篇博客中,对AIS数据进行压缩用了两种方法: 1.AIS数据压缩-时间比率算法(Time-Ratio-algorithm) 2.AIS数据压缩-时间比率_速度_航向算法(Time-Speed-H ...

  3. 贪心算法解决最优装载问题c语言,贪心算法解决最优装载问题

    <贪心算法解决最优装载问题>由会员分享,可在线阅读,更多相关<贪心算法解决最优装载问题(4页珍藏版)>请在人人文库网上搜索. 1.author : Kevin Black/这个 ...

  4. C语言回溯算法解决N皇后问题

    回溯算法的模型是 x++, not satisfy ? x-- : continue. 代码中x作列号,y[x]保存第x列上皇后放置的位置. 1 #include<stdio.h> 2 # ...

  5. dp笔记:关于DP算法和滚动数组优化的思考

    从网上总结了一些dp的套路以及对滚动数组的一些思考,现记录如下,希望以后回顾此类算法时会有所帮助. 目录 1.DP算法经验 1.DP算法核心: 2.DP算法类别以及例题 例1:三步问题 例2:最小路径 ...

  6. DP算法-背包问题与线性DP问题(Acwing)

    目录 一.何为DP 二.背包问题 1.01背包问题 2.完全背包问题 3.多重背包问题 三.线性DP问题(典型例题与总结) 1.数字三角形问题 2.最长上升子序列 3.最长上升子序列Ⅱ 4.最长公共子 ...

  7. 贪心算法 背包问题代码 c语言,用贪心算法求解普通背包问题的C++代码

    用贪心算法求解普通背包问题的C++代码 2019年3月6日 125次阅读 来源: 贪心算法 #include #define  M  100 void display(int &n,doubl ...

  8. 蓝桥杯VIP试题<黑心药商>c++DP算法 01背包问题 (详细注释)

    问题描述 JiaoShou消灭了百变怪,为爱琳世界赢得了和平,但他突然发现自己没有升级,这就意味着必须去喝药补血.爱琳世界的NPC卖的药已经不能满足他的需求了,他找到了爱琳唯一的药贩子-药加钱.药加钱 ...

  9. c语言贪心算法背包问题_GGTalk 中的算法知识 01背包问题

    前几天 GGTalk 发了一期关于算法类的播客,主持人磊子和嘉宾 WAMaker 都分享了很有趣的算法经历.这一系列文会帮你梳理一下在这期电台中,你应该知道的知识点. 这一篇来聊聊博客中 WAMake ...

  10. 动态规划算法python_动态规划——DP算法(Dynamic Programing)

    一.斐波那契数列(递归VS动态规划) 1.斐波那契数列--递归实现(python语言)--自顶向下 递归调用是非常耗费内存的,程序虽然简洁可是算法复杂度为O(2^n),当n很大时,程序运行很慢,甚至内 ...

最新文章

  1. 【MFC系列1】之简单Win32程序
  2. 微信小程序---实现输入手机验证码功能
  3. TOPSIS与模糊Borda 的组合应用(以第二届大湾区杯和国赛为案例)
  4. 物盟解决安防监控的“理想与现实”
  5. 计算机科学引论2答案,计算机科学引论答案-20210311090508.docx-原创力文档
  6. python趣味编程_戏说《西游记》之Python趣味编程:第四回 拜师学艺 破盘中之谜...
  7. SuperMap BIM+GIS技术白皮书
  8. 【学习备忘录】ele项目的环境配置
  9. 免费注册Gmail邮箱
  10. presenting controller presented controller 如何区分
  11. 川农在线期末机考答案2020计算机,川农网院20秋《计算机网络》期末机考
  12. 什么是 Linux Mint,它比 Ubuntu 好在哪里?
  13. NASA全球生态系统动态调查激光雷达(GEDI)
  14. 大写汉字转为阿拉伯数字
  15. 图像灰度、亮度、强度区分
  16. 毕业设计-基于spring boot的智慧物业管理系统
  17. Shakira feat Lil Wayne - Give it up to me
  18. BCSP-玄子前端开发之JavaScript+jQuery入门CH13_表单校验
  19. sumproduct函数的深入理解
  20. 【计算机组成】计算机组成原理大纲含思维导图

热门文章

  1. vue前端页面通用模板梳理
  2. java xcap_java实现发布订阅
  3. 【学习笔记】深度学习理论基础
  4. 解决:error C1083: 无法打开包括文件: “opencv2/opencv.hpp”: No such file or directory
  5. xv6实验课程--系统调用
  6. php怎么改成npk,【原创】 npk 文件解包工具+源代码
  7. 如何在Word中输入带圈数字1-10的黑底白字和白底黑字的数字字符?
  8. 【预测模型】基于Elman神经网络预测电力负荷matlab代码
  9. AT89C51单片机的8位竞赛抢答器的protues仿真设计_倒计时可调
  10. 中石油职称计算机试题,中石油职称计算机水平考试复习题库22-职称计算机考试其它试卷与试题.pdf...