| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 数据结构与算法 -> 01背包详解 -> 正文阅读 |
|
[数据结构与算法]01背包详解 |
01背包一、问题引出现在一共有 N N N 件物品,第i(i从1开始)件物品的重量为 v [ i ] v[i] v[i] ,价值为 w [ i ] w[i] w[i] 。每个物品至多挑选一次,且在挑选出来的物品的总重量不超过 V V V 的情况下,能装入背包的物品的总价值和最大为多少 二、分析2.1 暴力思考对于每一个物品我们无非就两种选择, 选 和 不选 那么对于N件物品的抉择方案数就是 2 N 2^N 2N 种,当 N < 27 N < 27 N<27 左右的时候,貌似还可行,我们可以通过 暴力搜索 来找出最大的总价值,那么当 N N N 比较大的时候呢,这种暴力做法就不可取 2.1.1 暴搜代码
2.2 子问题分析对于01背包的子问题是什么呢,我们定义 f [ i ] [ j ] f[i][j] f[i][j] 表示从前 i i i 件物品中每件物品最多选一次且总重量不超过 j j j 的最大价值和,那么我们就能得到 f [ N ] [ V ] f[N][V] f[N][V] 就是我们要求的问题,即:从前N个物品中,每个物品至多挑选一次,且在挑选出来的物品的总重量不超过 V V V 的最大价值和 我们来看这个问题的特点:每件物品要么选择,要么不选 我们发现对于 f [ i ] [ j ] f[i][j] f[i][j] 这个子问题,是从前 i ? 1 i-1 i?1 件物品转移过来的,因为第 i i i 件物品要么选择要么不选,那么对应的前置状态就是 f [ i ? 1 ] [ j ? v [ i ] ] + w [ i ] f[i-1][j-v[i]] + w[i] f[i?1][j?v[i]]+w[i] 和 f [ i ? 1 ] [ j ] f[i-1][j] f[i?1][j] 为什么是这两个呢?
那么对于 f [ i ] [ j ] f[i][j] f[i][j] 来说就只需要在这两种方案中抉择就好了 那么我们就得到了01背包的状态转移方程: f [ i ] [ j ] = m a x ( f [ i ? 1 ] [ j ] , f [ i ? 1 ] [ j ? v [ i ] ] + w [ i ] ) f[i][j]=max(f[i-1][j],f[i-1][j-v[i]] + w[i]) f[i][j]=max(f[i?1][j],f[i?1][j?v[i]]+w[i]) 这个状态转移方程非常重要,因为你学到后面会发现基本上所有的背包问题,例如:完全背包、多重背包、混合背包等等一些列背包都是通过01背包衍生出来的 参考代码
2.3 复杂度分析2.3.1 时间复杂度我们会发现时间复杂度是 O ( N V ) O(NV) O(NV) 的,并不能对上式做一些优化,但是空间复杂度却可以 2.3.2 空间复杂度分析上式的空间复杂度为: O ( N V ) O(NV) O(NV) ,但是其实空间复杂度还可以优化到 O ( V ) O(V) O(V) 2.3.2.1 01滚动优化我们继续来分析上面的转移方程,我们只用到了两层状态,第 i ? 1 i-1 i?1 层和第 i i i 层的,那么我们只需要每次存储这两层的状态就好啦,这也叫做 滚动数组 的 01滚动优化 2.3.2.2 就地滚动优化但是上面的 01滚动 并不能满足我们,因为每一次需要对当前的第i层状态进行一个备份,会增大我们的常数,这个时候我们就有一种更好的滚动优化 -> 就地滚动 ,什么意思呢?也就是我们只需要开一个空间大小为 V V V 的数组即可,但是这里需要注意,此时我们 V V V 的遍历方式需要 从后往前 ,我们来思考为什么,因为如果我们仍然从前往后遍历的话,对于每一个旧的状态就会被新的状态覆盖了,那么此时我们用到的状态就不是上一层的状态了而是被这一层覆盖了的状态,换句话说此时的 f [ j ] f[j] f[j] 不能从 i ? 1 i-1 i?1 层转移过来,这个操作就相当于对于每一件物品可以选择 无限次 的效果了,而这正是 完全背包 的就地滚动优化 2.3.2.3 滚动优化代码01滚动:
就地滚动:
2.4 记忆化搜索上面我们分析了一种暴力的做法,在通过分析子问题后,我们可以稍微优化一下,写成这样:
但是这样仍然会超时,我们分析会发现对于某些状态其实我们重复多次访问了,造成了时间复杂度非常高,所以我们通过记忆化搜索,将我们搜到的状态都记录下来,第二次访问的时候直接返回即可:
其实关于上面的搜索部分我们可以写的更简单一点:
或者从后往前递归:
三、一些细节3.1 初始化细节我们在求解关于01背包的问题的时候,大体分为两种问题,这两种问题的初始化方式都不太一样:
对于第一种背包,可以来参考这道题的题解:acmer.blog.csdn.net/article/details/122839724 对于第二种背包,可以参考这道题的题解:acmer.blog.csdn.net/article/details/122989979 3.2 常数细节注意我们上面的代码在内层的背包容量遍历的时候是直接从 V V V 遍历到 v [ i ] v[i] v[i] 的,而不是1,这样在 v [ i ] v[i] v[i] 和 V V V 比较大的时候常数会小一点 四、拓展4.1 超大背包对于这种炸空间的背包,我们意外发现n的范围只有 40 40 40 ,于是我们可以把这些物品拆分成两堆,然后我们用二进制枚举两个堆的物品,时间复杂度为 O ( n ? 2 ( n / 2 ) ) O(n?2(n/2)) O(n?2(n/2)) 在枚举第二堆的时候我们就可以在第一个堆里通过二分搜索找到满足条件物品堆,然后选择最优的结果就行每次二分查询时间 l o g ( m ) log(m) log(m) , m m m 表示的是第一堆物品有效个数,具体请看代码讲解
4.2 逆向01背包来自牛客寒假训练营的一道题: 4.2.1 思路我们现在是已经知道每个重量的瓜的个数,出现质量和为奇数是由于我们购买半个瓜的操作 4.2.2 拓展来自出题人的想法: 4.2.3 代码
五、题单 |
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/26 16:24:27- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |