目录

  • 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]也有两种情况

  1. 不装入第i种物品,即dp[i−1][j],同01背包;

  2. 装入第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])

此时的时间复杂度OnC变为:nCCwnC\frac{C}{w}nCwC​

方法三:转换成01背包

01背包问题是最基本的背包问题,我们可以考虑把完全背包问题转化为01背包问题来解:将一种物品转换成若干件只能装入0件或者1件的01背包中的物品。

最简单的想法是,考虑到第 i 种物品最多装入 C/w[i] 件,于是可以把第 i 种物品转化为 C/w[i] 件费用及价值均不变的物品,然后求解这个01背包问题。

更高效的转化方法是采用二进制的思想:把第 i 种物品拆成重量为 wa2kw_a2^kwa​2k 、价值为va2kv_a2^kva​2k的若干件物品,其中 k 取遍满足wa2k<Cw_a2^k<Cwa​2k<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背包

和前文方法一样,不过我感觉方法一已经够。

背包问题详解-动态规划相关推荐

  1. 0-1背包问题详解-动态规划-两种方法

    问题描述: 给定n种物品和一背包.物品i的重量为wi,其价值为vi, 背包容量为c.问应如何选择装入背包中的物品,使得背入背包的物品的总价值最大? 解析: 此问题形式化的描述是,给定c > 0, ...

  2. 详解动态规划01背包问题--JavaScript实现

    对其他动态规划问题感兴趣的,也可以查看 详解动态规划最少硬币找零问题--JavaScript实现 详解动态规划最长公共子序列--JavaScript实现 一开始在接触动态规划的时候,可能会云里雾里,似 ...

  3. c语言背包问题装字母,C语言动态规划之背包问题详解

    01背包问题 给定n种物品,和一个容量为C的背包,物品i的重量是w[i],其价值为v[i].问如何选择装入背包的物品,使得装入背包中的总价值最大?(面对每个武平,只能有选择拿取或者不拿两种选择,不能选 ...

  4. 详解动态规划最长公共子序列--JavaScript实现

    前面两篇我们讲解了01背包问题和最少硬币找零问题.这篇将介绍另一个经典的动态规划问题--最长公共子序列.如果没看过前两篇,可点击下面链接. 详解动态规划最少硬币找零问题--JavaScript实现 详 ...

  5. java动态规划凑硬币问题,详解动态规划最少硬币找零问题--JavaScript实现

    硬币找零问题是动态规划的一个经典问题,其中最少硬币找零是一个变种,本篇将参照上一篇01背包问题的解题思路,来详细讲解一下最少硬币找零问题.如果你需要查看上一篇,可以点击下面链接: 详解动态规划01背包 ...

  6. 01背包问题详解(浅显易懂)

    01背包问题详解 01背包是一种动态规划问题.动态规划的核心就是状态转移方程,本文主要解释01背包状态转移方程的原理. 问题描述 01背包问题可描述为如下问题: 有一个容量为V的背包,还有n个物体.现 ...

  7. 0-1背包问题详解(一步一步超详细)

    1.什么叫01背包问题? 背包问题通俗的说,就是假如你面前有5块宝石分别为a, b, c, d, e,每块宝石的重量不同,并且每块宝石所带来的价值也不同(注意:这里宝石的重量的价值没有特定关系),目前 ...

  8. 详解动态规划算法(Python)

    视频来源:https://www.bilibili.com/video/BV1xb411e7ww?from=search&seid=3112459103674479435 动态规划解题四组成部 ...

  9. 从背包问题优化详解动态规划思想

    动态规划: 所有的数据结构与算法的理解必须建立在题目的练习上,否则看多少理论都没有实际用处!!! 所以下面这些理论文字看不懂通通没关系,跟随下面的背包问题还会跟深入的理解. 一.基本概念:任何数学递推 ...

最新文章

  1. type和object
  2. Tomcat 配置安装
  3. linux c下,从路径名中分离文件名
  4. [译] 12步轻松搞定python装饰器 - 简书
  5. 支付宝(即时到账批量退款业务错误码)
  6. LeetCode 98验证二叉搜素树(中序遍历)99恢复二叉搜索树
  7. 《深入理解Nginx:模块开发与架构解析》一1.6 Nginx的命令行控制
  8. PHP 国家时区 PHP List of timezones (Not sorted by country)
  9. android关闭系统弹窗,Android 禁止 EditText 弹出软件盘
  10. 非刚性配准(Non-rigid ICP )
  11. 怎样才能算是在技术上活跃的小公司
  12. win11使用win10右键菜单的四种办法
  13. Learning optical flow from still images
  14. boost.asio 源码剖析
  15. TT 的旅行日记 Week7作业B题
  16. 【Letcode】机器人大冒险python3实现
  17. 她做销售6年,从底薪3K转行程序员狂飙2W,用两年转行经历致想转行的你
  18. 中空介孔载银二氧化硅聚苯乙烯微球/核壳聚苯乙烯/介孔二氧化硅微球/环氧树脂复合材料的制备
  19. 字符串匹配 (KMP)
  20. C 语言 rand() 和 srand() 使用方法

热门文章

  1. 自定义线性菜单 LinearMenu 仿触手tv菜单效果
  2. 浪涌保护器,电涌保护器SPD的工作原理
  3. Linux内核原子操作(1)基本原理
  4. LPG-PCA算法实现与详解
  5. Mtlab中的小括号()、中括号[]、大括号{}的使用及区别
  6. 苹果推出新款iPhone SE 拼多多只要2899元 苹果真的卖不动了吗?
  7. 打篮球戴什么耳机比较好、分享五款专门打篮球用的蓝牙耳机
  8. emacs如何显示行号
  9. 【荣耀笔试8.30】1.六位数最大时间。2.流水线组装产品所需时间。3.数字字符串组合倒序。
  10. MSDN 教学短片 WPF 12(画布)