动态规划,0/1背包,完全背包
Posted void-lambda
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了动态规划,0/1背包,完全背包相关的知识,希望对你有一定的参考价值。
动态规划
动态规划问题的一般形式就是求最值。
求解动态规划的核心问题是穷举。
动态规划的穷举有点特别,因为这类问题存在「重叠子问题」,如果暴力穷举的话效率会极其低下,所以需要「备忘录」或者「DP table」来优化穷举过程,避免不必要的计算。
而且,动态规划问题一定会具备「最优子结构」,才能通过子问题的最值得到原问题的最值。
关键就是状态转移方程,写出正确的状态转移方程,才能正确地枚举。
解决问题步骤:
明确「状态」 -> 定义 dp 数组/函数的含义 -> 明确「选择」(做出选择改变当前状态-> 明确 base case。
0/1背包问题
描述:
有N件物品和一个容量为V的背包。第i件物品的费用(即体积,下同)是w[i],价值是val[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
思路:
用动态规划的思路,阶段就是“物品的件数”,状态就是“背包剩下的容量”,那么很显然f [ i , v ] 就设为从前 i 件物品中选择放入容量为 v?的背包最大的价值。那么状态转移方程为:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? f[i][v]=max{ f[i-1][v],f[i-1][v-w[i]]+val[i] }。
这个方程可以如下解释:只考虑子问题“将前 i 个物品放入容量为 v 的背包中的最大价值”那么考虑如果不放入 i ,最大价值就和 i 无关,就是 f[ i - 1 ][ v ] ,?如果放入第 i 个物品,价值就是 f[ i - 1][ v - w[i] ] + val[ i ],我们只需取最大值即可。
优化:
上述状态表示,我们需要用二维数组,但事实上我们只需要一维的滚动数组就可以递推出最终答案。考虑到用f[ v ]来保存每层递归的值,由于我们求f[ i ][ v ] 的时候需要用到的是f[ i-1 ][ v] 和 f[ i-1 ][v - w[i] ] 于是可以知道,只要我们在求f[ v ]时不覆盖f[ v - w[i] ],那么就可以不断递推至所求答案。所以我们采取倒序循环,即v = m(m为背包总容积)伪代码如下:
for i = 1..N
for v = V..0
f[ v ] = max{ f[ v ],f[ v-w[i] ]+val[ i ] };
完全背包问题
描述:
有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的费用是w[i],价值是val[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
思路:
完全背包问题与0/1背包问题不同之处在于其每个物品是无限的,从每种物品的角度考虑,与它相关的策略就变成了取0件、1件、2件...。我们可以根据0/1背包的思路,对状态转移方程进行改进,令f[i][v]表示前 i 种物品恰放入一个容量为 v 的背包的最大权值。状态转移方程就变成了:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??f[?i?][ v ] = max{ f[ i-1 ][ v-k*w[i] ] + kval[?i?]? | 0 <= kw[i] <=? v}。
我们通过对0/1背包的思路加以改进,就得到了完全背包的一种解法,这种解法时间复杂度为O(n^3),空间复杂度为O(n^2)。
时间优化:
根据上述f[ i ][ v?]的定义,其为前 i 种物品恰好放入容量为 v 的背包的最大权值。根据上述状态转移方程可知,我们假设的是子结果f[ i-1 ][ v-k*w[i] ]中并没有选入第 i 种物品,所以我们需要逆序遍历(像0/1背包一样)来确保该前提;但是我们现在考虑“加选一件第 i 种物品”这种策略时,正需要一个可能已经选入第 i 种物品的子结果f[ i ][ v-w[i] ],于是当我们顺序遍历时,就刚好达到该要求。这种做法,使我们省去了一层循环,即第 i 种物品放入的件数k,从而时间复杂度优化为O(n^2)。
空间优化:
正如0/1背包的空间优化,上述状态转移方程已经优化为:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?f[i][v]=max{f[i-1][v],f[i][v-w[i]]+val[i]}
将这个方程用一维数组实现,便得到了如下伪代码:
for i?= 1..N
for v = 0..V
f[v] = max{f[v],f[v-w[i]] + val[ i?] };
以上是关于动态规划,0/1背包,完全背包的主要内容,如果未能解决你的问题,请参考以下文章