背包问题详解-动态规划
目录
- 01背包问题
- 暴力法
- 空间优化
- 背包问题的所有情况
- 完全背包问题
- 状态转移方程分析
- 空间优化
- 方法二:记录第i件物品的数据k
- 方法三:转换成01背包
- 多重背包
- 方法一:记录第i件物品的数据k
- 方法二:转换成01背包
前言
网上的背包问题已经写了很多,但是感觉不是很详细,很多细节虽然提了一下,但是没有讲透彻,因此我写下这篇笔记。
——————————————————————————————————————
背包问题(knapsack problem)是一个非常典型的考察动态规划应用的题目,对其加上不同的限制和条件,可以衍生出诸多变种,若要全面理解动态规划,就必须对背包问题了如指掌。
01背包问题
最基本问题描述指01背包问题:
一共有N件物品,第i(i从1开始)件物品的重量为w[i],价值为v[i]。在总重量不超过背包承载上限C的情况下,能够装入背包的最大价值是多少?
暴力法
我们的目标是书包内物品的总价值,而变量是物品和书包的限重,所以我们可定义状态dp:
dp[i][j] 表示将前i件物品装进限重为j的背包可以获得的最大价值, 0<=i<=N, 0<=j<=W
首先将dp[0][0]、dp[0][1]....dp[0][W]
初始化为0,表示将前0个物品(即没有物品)装入书包的最大价值为0。那么当 i > 0 时dp[i][j]
有两种情况:
- 1 不装入第i件物品,即
dp[i−1][j]
; - 2 装入第i件物品(前提是能装下),即
dp[i−1][j−w[i]] + v[i]
。
即状态转移方程为:
dp[i][j] = max(dp[i−1][j], dp[i−1][j−w[i]]+v[i]) // j >= w[i]
注意这里要保证j>=w[i]
,因此我们可以得到:
int knapsack(vector<int> v, vector<int> w, int n, int C )
{//初始化dp的值为0vector<vector<int> > dp(n, vector<int>(C + 1,0));for(int i = 1; i < =n; i++){for(int j = 1; j <= C ; j++){if(j < w[i]){dp[i][j] = f[i - 1][j]}else{dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i]);}}}return dp[n ][C];
}
此时的时间和空间复杂度都是O(nC)
空间优化
我们来看一下这个程序是怎么运行的,如图所示,
N=5,C=11, value ={1,6,18,22,28},weight={1,2,5,6,7}
程序首先求dp[1][1]=1, dp[1][2]=1......dp[1][11]=1
这一行的数据,之后依次求得每一行的数据。
最后,我们有用的数据是dp[n ][C]
,也就是说,很多数据我们可以不用保存,这就是空间复杂度可以优化到O(C)
的原理。
int knapsack(vector<int> v, vector<int> w, int n, int C)
{vector<int> dp(C + 1);for(int i = 0; i < =n; i++){for(int j = C; j >= w[i]; j--)dp[j] = max(dp[j], dp[j - w[i]] + v[i]);}return dp[C];
}
我们注意到for(int j = C; j >= w[i]; j--)
是反序的,为什么?
为了防止上一层循环的dp[0,…,j-1]被覆盖,循环的时候 j 只能逆向枚举(空间优化前没有这个限制)
原来的代码是dp[i][j] = max(dp[i−1][j], dp[i−1][j−w[i]]+v[i]) // j >= w[i]
也就是说,求dp[i][j]
的时候要用到之前的dp[i][j-1]
如图所示,如果已经有了i=1时候的的dp,下一步,求i=2时候的dp,我们求哪个数开始呢? 是dp[2][1]=1
还是dp[2][11]=7
。
显然是dp[2][11]
,也就是dp[11]
,
dp[i][j] = max(dp[i−1][j], dp[i−1][j−w[i]]+v[i]) // j >= w[i]
带入i=2,j=11
dp[2][11] = max(dp[1][11], dp[1][11−w[2]]+v[2])
求dp[2][11]
需要有dp[1][11],dp[1][11−w[2]]
,这是i=1循环时候求的值,不能先覆盖了。
即求dp[11]
要用到dp[10]
,dp[10]
是没有更新的,需要原来的dp[1][10]
,这样才能走通整个流程,所以逆向枚举。
——————————————————————————————————————————
背包问题的所有情况
背包类型还有以下几种:
1、0/1背包问题:每个元素最多选取一次
2、完全背包问题:每个元素可以重复选择
3、多重背包问题:不止一个背包,需要遍历每个背包
而每个背包问题要求的也是不同的,按照所求问题分类,又可以分为以下几种:
1、最值问题:要求最大值/最小值
2、存在问题:是否存在…………,满足…………
3、组合问题:求所有满足……的排列组合
因此把背包类型和问题类型结合起来就会出现以下细分的题目类型:
1、0/1背包最值问题
2、0/1背包存在问题
3、0/1背包组合问题
4、完全背包最值问题
5、完全背包存在问题
6、完全背包组合问题
…
完全背包问题
完全背包(unbounded knapsack problem)与01背包不同就是每种物品可以有无限多个:
一共有N种物品,每种物品有无限多个,第i(i从1开始)种物品的重量为w[i],价值为v[i]。在总重量不超过背包承载上限C的情况下,能够装入背包的最大价值是多少?
状态转移方程分析
我们的目标和变量和01背包没有区别,所以我们可定义与01背包问题几乎完全相同的状态dp:
dp[i][j]表示将前i种物品装进限重为j的背包可以获得的最大价值, 0<=i<=N, 0<=j<=C
初始状态也是一样的,我们将dp[0][0] ,dp[0][1]....dp[0][C]
初始化为0,表示将前0种物品(即没有物品)装入书包的最大价值为0。那么当 i > 0 时dp[i][j]也有两种情况
不装入第i种物品,即
dp[i−1][j]
,同01背包;装入第i种物品,此时和01背包不太一样,因为每种物品有无限个(但注意书包限重是有限的),所以此时不应该转移到
dp[i−1][j−w[i]]
而应该转移到dp[i][j−w[i]]
,即装入第i种商品后还可以再继续装入第种商品。(这点可以再想一想)
所以状态转移方程为
dp[i][j] = max(dp[i−1][j], dp[i][j−w[i]]+v[i]) // j >= w[i]
空间优化
这个状态转移方程与01背包问题唯一不同就是max第二项不是dp[i-1]
而是dp[i]
。
和01背包问题类似,也可进行空间优化,优化后不同点在于这里的 j 只能正向枚举而01背包只能逆向枚举,因为这里的max第二项是dp[i]
而01背包是dp[i-1]
,即这里就是需要覆盖而01背包需要避免覆盖。所以伪代码如下:
int knapsack(vector<int> v, vector<int> w, int n, int C)
{vector<int> dp(C + 1);for(int i = 0; i < =n; i++){for(int j = w[i]; j <= C; j--)dp[j] = max(dp[j], dp[j - w[i]] + v[i]);}return dp[C];
}
由上述代码看出,01背包和完全背包问题此解法的空间优化版解法唯一不同就是前者的 j 只能逆向枚举而后者的 j 只能正向枚举,这是由二者的状态转移方程决定的。
我们还是看之前的图例
dp[2][11]=7
如何来的:
dp[i][j] = max(dp[i−1][j], dp[i][j−w[i]]+v[i])
带入i=2,j=11
dp[2][11] = max(dp[2][11], dp[2][11−w[2]]+v[2])
需要 dp[2][11−w[2]]
,也就在循环i=2
里面,先更新dp[11−w[2]]
,再更新dp[11]
即 j
要先小后大,升序,正向枚举。
方法二:记录第i件物品的数据k
除了上面的思路外,完全背包还有一种常见的思路,但是复杂度高一些。我们从装入第 i 种物品多少件出发,01背包只有两种情况即取0件和取1件,而这里是取0件、1件、2件…直到超过限重(k > j/w[i]),所以状态转移方程为:
# k为装入第i种物品的件数, k <= j/w[i]
dp[i][j] = max{(dp[i-1][j − k*w[i]] + k*v[i]) for every k}
同理也可以进行空间优化,需要注意的是,这里max里面是dp[i-1],和01背包一样,所以 j 必须逆向枚举,优化后代码为:(写外代码了)
/ 完全背包问题思路二伪代码(空间优化版)
dp[0,...,W] = 0
for i = 1,...,Nfor j = C,...,w[i] // 必须逆向枚举!!!for k = [0, 1,..., j/w[i]]dp[j] = max(dp[j], dp[j−k*w[i]]+k*v[i])
此时的时间复杂度O从nC
变为:nCCwnC\frac{C}{w}nCwC
方法三:转换成01背包
01背包问题是最基本的背包问题,我们可以考虑把完全背包问题转化为01背包问题来解:将一种物品转换成若干件只能装入0件或者1件的01背包中的物品。
最简单的想法是,考虑到第 i 种物品最多装入 C/w[i]
件,于是可以把第 i 种物品转化为 C/w[i]
件费用及价值均不变的物品,然后求解这个01背包问题。
更高效的转化方法是采用二进制的思想:把第 i 种物品拆成重量为 wa2kw_a2^kwa2k 、价值为va2kv_a2^kva2k的若干件物品,其中 k 取遍满足wa2k<Cw_a2^k<Cwa2k<C的非负整数。这是因为不管最优策略选几件第 i 种物品,总可以表示成若干个刚才这些物品的和(例:13 = 1 + 4 + 8)。这样就将转换后的物品数目降成了对数级别。
多重背包
多重背包(bounded knapsack problem)与前面不同就是每种物品是有限个:
一共有N种物品,第i(i从1开始)种物品的数量为n[i],重量为w[i],价值为v[i]。在总重量不超过背包承载上限W的情况下,能够装入背包的最大价值是多少?
方法一:记录第i件物品的数据k
此时的分析和完全背包的方法二差不多,也是从装入第 i 种物品多少件出发:装入第i种物品0件、1件、…n[i]件(还要满足不超过限重)。所以状态方程为:
# k为装入第i种物品的件数, k <= min(n[i], j/w[i])
dp[i][j] = max{(dp[i-1][j − k*w[i]] + k*v[i]) for every k}
同理也可以进行空间优化,而且 j 也必须逆向枚举,优化后代码为:(写伪代码了
// 完全背包问题思路二伪代码(空间优化版)
dp[0,...,C] = 0
for i = 1,...,Nfor j = C,...,w[i] // 必须逆向枚举!!!for k = [0, 1,..., min(n[i], j/w[i])]dp[j] = max(dp[j], dp[j−k*w[i]]+k*v[i])
方法二:转换成01背包
和前文方法一样,不过我感觉方法一已经够。
背包问题详解-动态规划相关推荐
- 0-1背包问题详解-动态规划-两种方法
问题描述: 给定n种物品和一背包.物品i的重量为wi,其价值为vi, 背包容量为c.问应如何选择装入背包中的物品,使得背入背包的物品的总价值最大? 解析: 此问题形式化的描述是,给定c > 0, ...
- 详解动态规划01背包问题--JavaScript实现
对其他动态规划问题感兴趣的,也可以查看 详解动态规划最少硬币找零问题--JavaScript实现 详解动态规划最长公共子序列--JavaScript实现 一开始在接触动态规划的时候,可能会云里雾里,似 ...
- c语言背包问题装字母,C语言动态规划之背包问题详解
01背包问题 给定n种物品,和一个容量为C的背包,物品i的重量是w[i],其价值为v[i].问如何选择装入背包的物品,使得装入背包中的总价值最大?(面对每个武平,只能有选择拿取或者不拿两种选择,不能选 ...
- 详解动态规划最长公共子序列--JavaScript实现
前面两篇我们讲解了01背包问题和最少硬币找零问题.这篇将介绍另一个经典的动态规划问题--最长公共子序列.如果没看过前两篇,可点击下面链接. 详解动态规划最少硬币找零问题--JavaScript实现 详 ...
- java动态规划凑硬币问题,详解动态规划最少硬币找零问题--JavaScript实现
硬币找零问题是动态规划的一个经典问题,其中最少硬币找零是一个变种,本篇将参照上一篇01背包问题的解题思路,来详细讲解一下最少硬币找零问题.如果你需要查看上一篇,可以点击下面链接: 详解动态规划01背包 ...
- 01背包问题详解(浅显易懂)
01背包问题详解 01背包是一种动态规划问题.动态规划的核心就是状态转移方程,本文主要解释01背包状态转移方程的原理. 问题描述 01背包问题可描述为如下问题: 有一个容量为V的背包,还有n个物体.现 ...
- 0-1背包问题详解(一步一步超详细)
1.什么叫01背包问题? 背包问题通俗的说,就是假如你面前有5块宝石分别为a, b, c, d, e,每块宝石的重量不同,并且每块宝石所带来的价值也不同(注意:这里宝石的重量的价值没有特定关系),目前 ...
- 详解动态规划算法(Python)
视频来源:https://www.bilibili.com/video/BV1xb411e7ww?from=search&seid=3112459103674479435 动态规划解题四组成部 ...
- 从背包问题优化详解动态规划思想
动态规划: 所有的数据结构与算法的理解必须建立在题目的练习上,否则看多少理论都没有实际用处!!! 所以下面这些理论文字看不懂通通没关系,跟随下面的背包问题还会跟深入的理解. 一.基本概念:任何数学递推 ...
最新文章
- type和object
- Tomcat 配置安装
- linux c下,从路径名中分离文件名
- [译] 12步轻松搞定python装饰器 - 简书
- 支付宝(即时到账批量退款业务错误码)
- LeetCode 98验证二叉搜素树(中序遍历)99恢复二叉搜索树
- 《深入理解Nginx:模块开发与架构解析》一1.6 Nginx的命令行控制
- PHP 国家时区 PHP List of timezones (Not sorted by country)
- android关闭系统弹窗,Android 禁止 EditText 弹出软件盘
- 非刚性配准(Non-rigid ICP )
- 怎样才能算是在技术上活跃的小公司
- win11使用win10右键菜单的四种办法
- Learning optical flow from still images
- boost.asio 源码剖析
- TT 的旅行日记 Week7作业B题
- 【Letcode】机器人大冒险python3实现
- 她做销售6年,从底薪3K转行程序员狂飙2W,用两年转行经历致想转行的你
- 中空介孔载银二氧化硅聚苯乙烯微球/核壳聚苯乙烯/介孔二氧化硅微球/环氧树脂复合材料的制备
- 字符串匹配 (KMP)
- C 语言 rand() 和 srand() 使用方法
热门文章
- 自定义线性菜单 LinearMenu 仿触手tv菜单效果
- 浪涌保护器,电涌保护器SPD的工作原理
- Linux内核原子操作(1)基本原理
- LPG-PCA算法实现与详解
- Mtlab中的小括号()、中括号[]、大括号{}的使用及区别
- 苹果推出新款iPhone SE 拼多多只要2899元 苹果真的卖不动了吗?
- 打篮球戴什么耳机比较好、分享五款专门打篮球用的蓝牙耳机
- emacs如何显示行号
- 【荣耀笔试8.30】1.六位数最大时间。2.流水线组装产品所需时间。3.数字字符串组合倒序。
- MSDN 教学短片 WPF 12(画布)