三种背包问题+滚动数组优化

三种背包问题+滚动数组优化

滚动数组

滚动数组简单的理解就是让数组滚动起来,起到节省存储空间的作用。主要应用在递推或动态规划中。

举个适合使用滚动数组的例子:
斐波那契数列为1、1、2、3、5、8、13、21、34……此数列从第3项开始,每一项都等于前两项之和。
递推公式为F(n)=F(n-1)+F(n-2),n≥3,F(1)=1,F(2)=1。

假如我们要求斐波拉切数列的第80项,我们常规的方法是定义一个80大小的数组,然后进行如下的循环。

在这里插入图片描述

那么我们要求第2e7项的斐波拉契,我们不可能去定义一个2e7的数组。通过观察,我们可以发现其实每一次循环都只用到前两位的数组,使用过后就不会使用了,很浪费空间。我们可以使用滚动数组来解决。

使用滚动数组的话,我们就只需要定义一个3大小的数组(仔细想想我们滚动话只需要3个数就够了)。

在这里插入图片描述

滚动数组的使用重在对于取余(%)的理解下面来演示一下。

无论多大的数取余某个数时,得到的数都在0到这个数之间。当下标对循环长度取余,则对于n长度的环,第n-1个的下一个将会是第0个,从而实现了循环。

在这里插入图片描述
在这里插入图片描述
滚动数组只能节约空间复杂度,时间复杂度还是和原来一样。

01背包

描述:
有n件物品和一个容量为m的背包。第i件物品的费用是v[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

这是最基础的背包问题,特点是:每种物品仅有一件,可以选择放或不放。

状态转移方程:

二维数组

用 dp[i][j]表示前i种物品放入一个容量为j的背包的最大价值
dp[i][j]=j<w[i]? dp[i-1][j]:max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]); (1<=i<=n,0<=j<=m)

在这里插入图片描述
下面通过图表样例理解下
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
此时我们可以发现我们得到了这么一个数组,dp[i][j]即表示在前i个物品中选取任意物品装填承重j背包时可得到的最大价值和。

通过对上面图表的分析理解我们发现,我们只需要前两行的值就能得出新的一行,和斐波拉契很相似,我们可以使用滚动数组。

滚动数组

代码解析:
在这里插入图片描述
但是仅仅滚动数组的优化通常来说是不够的。毕竟不管怎么样还是一个二维数组,优化始终是有限的。

优化版

用dp[j]表示当前总重量为j的所有方案中的最大价值
dp[j]=max(dp[j],dp[j-w[i]]+v[i]); (1<=i<=n ,w[i]<=j<=W)

代码解析:
在这里插入图片描述

这里的一维数组的优化是最优的。

核心是基于一维数组做反向迭代,这样的话j<w[i]的部分直接不用复制了,且由于是反向的线性更新也不会发生冲突。

举个例子

在这里插入图片描述

在这里插入图片描述
结合动图理解这里的反向迭代的巧妙。

完全背包

描述:
N种物品,第i种的物品重量是w[i],价值是v[i],每种物品有无限件,求总重量在背包承重m以内的最大总价值。

状态转移方程:

二维数组

用dp[i][j]表示前i种物品放入一个容量为j的背包的最大价值类似于简化为01背包。
dp[i][j] = max(dp[i][j], dp[i-1][j-kw[i]] + kv[i]]) (1<=i<=n,0<=j<=m) (0 <= k*w[i] <= j)

代码解析:

在这里插入图片描述

这里可以理解为变相的01背包,重量为kw[i],价值为kv[i]。

小优化版:用dp[i][j]表示前i种物品放入一个容量为j的背包的最大价值,
dp[i][j] = max(dp[i-1][j],dp[i][j-w[i]+v[i]) (1<=i<=n,0<=j<=W)

代码解析:

在这里插入图片描述

核心在于将已装载的dp[i][j]用于后续dp,因为物品无限件,同一物品可以选取多次。这是其与01背包的主要区别。

01背包 dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);
完全背包 dp[i][j]=max(dp[i-1][j],dp[i][j-w[i]]+v[i]);

这个已装载怎么理解?
举个例子:
遍历到第5个物品,其重量是3:
那么dp[5][3]处在此次迭代中会存: 以3容量的背包选取前4种物品 和 选取一个桃子后以0容量的背包选取前四种物品中的最优情况。dp[i][j]=max(dp[i-1][j],dp[i][j-w[i]]+v[i]);

如果此时选择第5个的情况是最优情况,被存储了,那就表示已装载。因为能在后续的遍历中多次拿到第5个物品。

如果已经dp[5][3]拿到了第5个物品那么在后续的dp[5][6]只需要存: 以6容量的背包选取前4种物品 和 再拿一个第5个物品加上dp[5][3]中的最优情况。


看一下和01背包的区别
在01背包中:
假如遍历到第5个物品,其重量是3:
那么dp[5][3]在此处迭代会存:以3容量的背包选取前4种物品 和 选取一个桃子后以0容量的背包选取前四种物品中的最优情况。dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);
而dp[5][6]则是存以6容量的背包选取前4种物品 和 (以3容量的背包选取前4种物品+一个第5中物品的价值)的最优情况。
dp[5][6]与dp[5][3]没有直接的迭代关系。

滚动数组

完全背包的滚动数组和01背包类似。

代码解析:
在这里插入图片描述
但是仅仅滚动数组的优化通常来说是不够的。

优化版

用dp[j]表示当前总重量为j的所有方案中的最大价值
dp[j]=max(dp[j],dp[j-w[i]]+v[i]); (1<=i<=n,0<=j<=W)

代码解析:
在这里插入图片描述

核心在于类似01背包的反向迭代

举个例子

在这里插入图片描述

在这里插入图片描述

结合01背包的动图,理解其区别。

多重背包

描述
有n种物品,第i种物品的重量是w[i],价值是w[i],每种物品的数量有限k[i],如何在m重量内,让总价值尽可能最大。

普通的二维解法和完全背包很类似。

二维数组

代码解析:

在这里插入图片描述

这里与完全背包唯一不同的就是不但要满足背包大小,还要满足物品个数。

滚动数组

滚动数组多重背包

代码解析:

在这里插入图片描述

二进制优化

这里三个for循环复杂度太高,不适合很多题目,我们要进行优化。

首先我们要知道我们的目的是什么,目的是将单种物品分解为概念上的多个不同物品然后做01背包。而且分解不是乱分解的,我们需要做到首先不能重复,其次需要将所有的分解的情况都要表示出来,比如有10个x物品,我们需要把0-10的组合情况全部都能表示出来。

优化解法 (二进制优化)
1.如果限制的数量*物品容量>=当前最大容量
直接考虑成完全背包问题来处理,把物品数量当成无限数量来考虑。

2.如果限制的数量物品容量<当前最大容量
将单种物品二进制分解为概念上的多个不同物品然后做01背包
*

普通情况直接完全背包很好理解,特殊的二进制分解我们可以根据例子来理解:

假如给了我们 价值为 2,但是数量却是10 的物品,我们应该把10给拆开,要知道二进制能够表示任何数的,所以10 就是可以有1,2,4,8之内的数把它组成,一开始我们选上 1了,然后让10-1=9,再选上2,9-2=7,在选上 4,7-4=3,而这时的3<8了,所以我们就是可以得出 10由 1,2,4,3来组成。

我们能发现1,2,3,4能组成1-10以内的任何数。

那么他们的价值是什么呢,是2,4,6,8,也就说给我们的价值为2,数量是10的这批货物,已经转化成了价值分别是2,4,6,8元的货物了,那么最后在进行dp选择的时候,就能通过他们的最优解选择,组成出我们需要的组成物品。形成概念上的01背包。

在这里插入图片描述
在这里插入图片描述

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值