程序员必知:动态规划——背包问题1:01背包

简介: 程序员必知:动态规划——背包问题1:01背包

背包问题是动态规划中的一个经典题型,其实,也比较容易理解。

当你理解了背包问题的思想,凡是考到这种动态规划,就一定会得很高的分。

背包问题主要分为三种:

01背包 完全背包 多重背包

其中,01背包是最基础的,最简单的,也是最重要的。

因为其他两个背包都是由01背包演变而来的。所以,学好01背包,对接下来的学习很有帮助。

废话不多说,我们来看01背包。

01 背包问题:给定 n 种物品和一个容量为 C 的背包,物品 i 的重量是 wi,其价值为 vi 。

问:应该如何选择装入背包的物品,使得装入背包中的物品的总价值最大?

第一眼看上去,我们会想到贪心(背包问题还不会QAQ)。

用贪心算法来看,流程是这样的:

1.排序,按价值从大到小排序

2.选价值尽可能大的物品放入。

但是,贪心做这题是错的。

让我们举个反例:

n=5,C=10

重量 价值

第一个物品: 10 5

第二个物品: 1 4

第三个物品: 2 3

第四个物品: 3 2

第五个物品: 4 1

用贪心一算。答案是5,但正解是用最后4个,价值总和是10.

那将重量排序呢?

其实也不行。

稍微一想就想到了反例。

我们需要借助别的算法。

贪心法用的是一层循环,而数据不保证在一层循环中得解,于是,我们要采用二层循环。

这也是背包的思想之一。

来看背包算法:

1.用二维数组dp 【 i 】 【 j 】,表示在面对第 i 件物品,且背包容量为 j 时所能获得的最大价值

比如说上面的那个反例:

dp 【 1 】 【 3 】 = 4 + 3 = 7.

2.01背包之所以叫“01”,就是一个物品只能拿一次,或者不拿。

那我们就分别来讨论拿还是不拿。

(1)j < w【i】 的情况,这时候背包容量不足以放下第 i 件物品,只能选择不拿

dp 【 i 】 【 j 】 = dp 【 i - 1 】 【 j 】;

(2)j>=w【i】 的情况,这时背包容量可以放下第 i 件物品,我们就要考虑拿这件物品是否能获取更大的价值。

~如果拿取,dp 【 i 】 【 j 】 = dp 【 i - 1 】 【 j - w 【 i 】 】 + v 【 i 】。 这里的dp 【 i - 1 】 【 j - w 【 i 】 】指的就是考虑了i-1件物品,背包容量为 j-w【i】 时的最大价值,也是相当于为第i件物品腾出了w【i】的空间。

~如果不拿,dp 【 i 】 【 j 】 = dp 【 i-1 】 【 j 】

到底拿不拿呢?要看拿与不拿那个结果更大了。

看,这用到了动态规划的思想:在求值时会用到之前状态的结果。

我们就可以得出状态转移方程了。

1 if(j>=w【i】)

2 dp【i】【j】=max(dp【i-1】【j】,dp【i-1】【j-w【i】】+v【i】);

3 else

4 dp【i】【j】 = dp【i-1】【j】;

于是,完整代码就出来了:

code:

1 int n,c,w【1001】,v【1001】;

2 int dp【1001】【1001】;

3 cin]n]c;

4 for(int i=1;i<=n;i++)

5 cin]w【i】]v【i】;

6 for(int i=1;i<=n;i++) //物品

7 {

8 for(int j=1;j<=c;j++) //从一枚举到C

9 {

10 if(j>=w【i】)

11 dp【i】【j】=max(dp【i-1】【j】,dp【i-1】【j-w【i】】+v【i】); //最大值

12 else

13 dp【i】【j】=dp【i-1】【j】;

14 }

15 }

16 cout[dp【n】【c】[endl;//n个物品,背包空间为c的dp值

那么,这是二维的01背包,可以看出,数组受空间限制,不能开太大,所以,我们还有一种优化01背包,只有一维的数组

先考虑上面讲的基本思路如何实现,肯定是有一个主循环i=1..N,每次算出来二维数组dp【 i 】 【 0..V 】的所有值。那么,如果只用一个数组dp 【 0..V 】,能不能保证第i次循环结束后dp【 v 】中表示的就是我们定义的状态dp 【 i 】 【 v 】呢?dp【 i 】【 v 】是由dp【 i-1 】【 v 】和dp【 i-1 】【 v-c【i】 】两个子问题递推而来,能否保证在推dp【 i 】【 v 】时(也即在第i次主循环中推dp【 v 】时)能够得到dp【 i-1 】【 v 】和dp【 i-1 】【 v-c【i】 】的值呢?事实上,这要求在每次主循环中我们以v=V..0的顺序推dp【 v 】,这样才能保证推dp【 v 】时dp【 v-c【i】 】保存的是状态dp【 i-1 】【 v-c【i】 】的值。如果将v的循环顺序从上面的逆序改成顺序的话,那么则成了dp【 i 】【 v 】由dp【 i 】【 v-c【 i 】 】推知,与本题意不符,但它却是完全背包最简捷的解决方案,故学习只用一维数组解01背包问题是十分必要的。

1 for(int i=1;i<=n;i++)

2 {

3 for(int v=c;v>=0;v--)

4 {

5 dp【v】=max(dp【v】,dp【v-c【i】】+w【i】);

6 }//代码效果参考:http://www.ezhiqi.com/zx/art_3562.html

7 }//代码效果参考:http://www.ezhiqi.com/zx/art_6850.html

这就是代码,相比上面的简洁了许多,既优化了空间,又减少了代码长度。

这就是01背包问题,其实真没啥难度,记下模板都能过。

上面的讲解应该很详细了,大家多看几遍,应该是可以理解的。

我们下次将讲解完全背包和多重背包问题,我们下次见。

相关文章
|
数据可视化 前端开发 JavaScript
pyEcharts安装及详细使用指南(一)
pyEcharts安装及详细使用指南(一)
1999 0
pyEcharts安装及详细使用指南(一)
|
SQL 关系型数据库 MySQL
数据库导入SQL文件:全面解析与操作指南
在数据库管理中,将SQL文件导入数据库是一个常见且重要的操作。无论是迁移数据、恢复备份,还是测试和开发环境搭建,掌握如何正确导入SQL文件都至关重要。本文将详细介绍数据库导入SQL文件的全过程,包括准备工作、操作步骤以及常见问题解决方案,旨在为数据库管理员和开发者提供全面的操作指南。一、准备工作在导
1722 0
二叉树详解(深度优先遍历、前序,中序,后序、广度优先遍历、二叉树所有节点的个数、叶节点的个数)
二叉树详解(深度优先遍历、前序,中序,后序、广度优先遍历、二叉树所有节点的个数、叶节点的个数)
|
缓存 资源调度 JavaScript
Nodejs 命令行调用 exec 与 spawn 差异--- 解决 spawn yarn ENOENT error
Nodejs 命令行调用 exec 与 spawn 差异--- 解决 spawn yarn ENOENT error
|
SQL 安全 Shell
扫描神器:Netsparker 保姆级教程(附链接)
漏洞扫描神器:Netsparker 保姆级教程(附链接)
扫描神器:Netsparker 保姆级教程(附链接)
|
存储 人工智能 C语言
数据结构基础详解(C语言): 栈的括号匹配(实战)与栈的表达式求值&&特殊矩阵的压缩存储
本文首先介绍了栈的应用之一——括号匹配,利用栈的特性实现左右括号的匹配检测。接着详细描述了南京理工大学的一道编程题,要求判断输入字符串中的括号是否正确匹配,并给出了完整的代码示例。此外,还探讨了栈在表达式求值中的应用,包括中缀、后缀和前缀表达式的转换与计算方法。最后,文章介绍了矩阵的压缩存储技术,涵盖对称矩阵、三角矩阵及稀疏矩阵的不同压缩存储策略,提高存储效率。
934 9
|
存储 人工智能 C语言
C语言程序设计核心详解 第六章 数组_一维数组_二维数组_字符数组详解
本章介绍了C语言中的数组概念及应用。数组是一种存储同一类型数据的线性结构,通过下标访问元素。一维数组定义需指定长度,如`int a[10]`,并遵循命名规则。数组元素初始化可使用 `{}`,多余初值补0,少则随机。二维数组扩展了维度,定义形式为`int a[3][4]`,按行优先顺序存储。字符数组用于存储字符串,初始化时需添加结束符`\0`。此外,介绍了字符串处理函数,如`strcat()`、`strcpy()`、`strcmp()` 和 `strlen()`,用于拼接、复制、比较和计算字符串长度。
483 4
|
存储 算法 C语言
C语言手撕实战代码_二叉树_构造二叉树_层序遍历二叉树_二叉树深度的超详细代码实现
这段代码和文本介绍了一系列二叉树相关的问题及其解决方案。其中包括根据前序和中序序列构建二叉树、通过层次遍历序列和中序序列创建二叉树、计算二叉树节点数量、叶子节点数量、度为1的节点数量、二叉树高度、特定节点子树深度、判断两棵树是否相似、将叶子节点链接成双向链表、计算算术表达式的值、判断是否为完全二叉树以及求二叉树的最大宽度等。每道题目均提供了详细的算法思路及相应的C/C++代码实现,帮助读者理解和掌握二叉树的基本操作与应用。
352 2
|
C++
【洛谷 P1042】[NOIP2003 普及组] 乒乓球 题解(模拟+向量)
`NOIP2003`普及组编程题:乒乓球比赛模拟。给定一系列球赛记录(WL序列),程序需按11分和21分制分析比分。输入含多个字符串,含W(华华得分)、L(对手得分)和E(结束标记)。输出每局比分,分制间空行间隔。样例:`WWWWWW...` → `11:0\n11:0\n1:1`(11分制)和`21:0\n2:1`(21分制)。代码使用C++,逐字符读取,当分差≥2且得分≥x时输出比分。
258 0
|
C++
【洛谷 P1739】表达式括号匹配 题解(栈)
该编程题目要求检查给定的包含字母、运算符和括号的表达式是否括号匹配。输入为一行表达式,以`@`结束。如果括号匹配,输出`YES`,否则输出`NO`。样例包括一个匹配和一个不匹配的表达式。解决方案是使用栈,遇到左括号入栈,遇到右括号时判断栈是否为空,栈空则输出`NO`,否则出栈。当读到`@`时,栈空则输出`YES`,否则输出`NO`。提供的AC代码使用C++实现,通过`stack`处理括号匹配。
388 0