介绍:
背包问题:有一系列的物品,每个物品有自己的价值v和重量w,怎么选取物品放到有一定最大承受重量m的背包里面达到价值最大的问题。
- 如果每件物品只有一件:01背包
- 如果每件物品可以拿无数次:完全背包
- 如果第 i 种商品有 n 件:多重背包
- 如果将01背包、完全背包、多重背包混合起来。也就是说,有的物品只可以取一次(01背包),有的物品可以取无限次(完全背包),有的物品可以取的次数有一个上限(多重背包): 混合背包
- 二维费用背包问题
- 分组背包问题
- 有依赖的背包问题
背包问题
01背包问题
-
问题描述:一共有 i 种商品,每种商品的价值和重量是 v[i] w[i],怎么选取放到总容量是M的背包中以达到最大价值,每种商品只有一个。
-
问题解答思路:
假设 f [ i , m ] f [ i , m ] f[i,m] 就设为从前 i 种物品中选择放入当前容量为 m 的背包最大的价值。那么状态转移方程为:
f [ i , m ] = m a x { f [ i − 1 , m ] , f [ i − 1 , m − w [ i ] ] + v [ i ] } f [ i , m ]=max \left \{ f[i-1,m], \space \space f[i-1,m-w[i]]+v[i] \right \} f[i,m]=max{
f[i−1,m], f[i−1,m−w[i]]+v[i]}
解释:考虑子问题,将第i种商品放入背包:
自顶向下的思路 : f [ i , m ] = { f [ i − 1 , m ] :第i种物品不放 f [ i − 1 , m − w [ i ] ] + v [ i ] :放第i种物品 \textbf{ 自顶向下的思路 }: f[i,m]=\left\{ \begin{aligned} &f[i-1, m] \text{:第i种物品不放}\\ & f[i-1,m-w[i]]+v[i] \text{:放第i种物品} \end{aligned} \right. 自顶向下的思路 :f[i,m]={
f[i−1,m]:第i种物品不放f[i−1,m−w[i]]+v[i]:放第i种物品
如果第 i 种商品不放,那么最大价值就等于将前i-1种商品放入 容量为 m的背包;如果放第 i 种物品(前提是当前容量m > w[i]),则最大价值:将前 i-1 种商品放到 容量为 m-w[i]的最大价值+当前的v[i]
- 例子:
背包的容量 M= 8
商品 | 重量 | 价值 |
---|---|---|
1 | 2 | 3 |
2 | 3 | 4 |
3 | 4 | 5 |
4 | 5 | 6 |
示例代码:
int main(int argc, char* argv[])
{
int w[5] = {
0, 2, 3, 4, 5};
int v[5] = {
0, 3, 4, 5, 6};
int M = 8;
int dp[5][9];
memset(dp, 0, sizeof(dp));
for(int i=1; i<=4; i++) // 四种商品
{
for(int j=1; j<=M; j++) //包的容量
{
if(j<w[i]) // 当前的容量 < 当前的物品重量
dp[i][j] = dp[i-1][j]; // 不放当前第 i 种商品
else // 放第i种商品,取放与不放的最大值
dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i]]+v[i]);
}
}
// 输出动态规划表
for (int i = 0; i < 5; i++)
{
for (int j = 0; j < 9; j++)
{
cout << dp[i][j] << ' ';
}
cout << endl;
}
return 0;
}
结果:4种商品,背包容量8的最优解是:10
- 空间复杂度的优化
有n种商品,背包容量为M,以上方法的时间和空间复杂度均为O(NM),时间复杂度没法优化,但是空间复杂度依然可以优化到O(M),实际可以用一维数组 dp[m+1] 即可。外层循环的每次 i 初始值是上一轮递推得到的值(i - 1时的情况),需要注意的是我们必须倒序跟新, 这样才能保证推dp[j]时dp[j-w[i]]保存的是状态dp[i-1][j-w[i]] 的值,这样符合状态转移方程
int main(int argc, char* argv[])
{
int w[5] = {
0, 2, 3, 4, 5};
int v[5] = {
0, 3, 4, 5, 6};
int M = 8;
int dp[9];
memset(dp, 0, sizeof(dp));
for(int i=1; i<=4; i++) // 四种商品
{
for(int j=M; j>0; j--) //包的容量
{
if(j>=w[i]) // 当前的容量 >= 当前的物品重量
dp[j] = max(dp[j], dp[j-w[i]]+v[i]);
}
}
// 输出动态规划表
for (int j = 0; j < 9; j++)
{
cout << dp[j] << ' ';
}
return 0;
}
结果:
- 01背包拓展:
- 求取的不是价值的最大值而是最小值:把max换成min即可,原理相同。
- 求最优解有哪些物品:背包问题最优解回溯
- 要求“恰好装满背包”时的最优解背包九讲的01背包-初始化细节问题,例子,可以初始化 -999,有可能最后没有满足要求的方案,非法值就是 -大数
- 如果碰到物品的v[i]是带有负数或者小数,动态规划的数组申请为1维的,如果连重量w[i]是小数,申请为1维的动态规划的数组也不满足要求,因为小数没办法作为数组的下标:上面代码的 dp[j-w[i]],我们可以选择将背包容量和每个物品的重量同时乘以大数满足整数要求。
完全背包
-
问题描述:有N种物品和一个容量为M的背包,每种物品都有无限件可用。第i种物品的重量是w[i],价值是v[i]。求解将哪些物品装入背包可使这些物品的重量总和不超过背包容量,且价值总和最大。
-
问题解答思路:在选取第 i 种商品的时候,可以选 0, 1, 2 … n , 只要保证 n个商品的重量 <= 当前背包能承受的重量,所以按照01背包的假设:F[i][m]代表前i种商品放到容量m的背包中的最大价值,那么状态转移方程:
f [ i ] [ m ] = m a x { f [ i − 1 ] [ m − k ∗ w [ i ] ] + k ∗ v [ i ] } k ∗ w [ i ] < = m f[i][m] = max \left \{ f[i-1][m-k*w[i]] +k*v[i]\right \} \space \space \space \space \space \space k*w[i]<=m