下面是问题
背包问题是动态规划的经典问题之一。是指在一个有容积或重量限制的背包中放入物品,物品有体积、重量、价值等属性。要求在满足背包限制的情况下,如何放置物品,让背包中的物品价值之和最大。根据物品限制条件的不同,可分为01背包,完全背包,多重背包和分组背包等。
如果每个物品都有无数个,那么就是完全背包
如果每个物品都只有1个,要么放进去,要么不放进去,那么就是01背包
如果每个物品都有数量,但数量都是有限的,那么就是多重背包
如果把物品分组,每组只能选一个,那么就是分组背包
01背包
问题描述
给定n个物品,每个物品有重量$w_i$和价值$v_i$,每种物品有且只有一个。背包容量为W,要求在不超过背包容量的情况下,如何放置物品,让背包中物品的价值之和最大
假设第i阶段表示处理第i个物品,第i-1阶段表示处理第i-1个物品。当处理第i个物品时,前i-1个物品已处理完毕。只需要考虑第i-1阶段向第i阶段转义.
用$c[i][j]$表示前i件物品放入一个容量为j的背包可以获得的最大价值
对于第i个物品的处理状态:
那么当我们的背包容量都不能够放下当前的物品的时候,那么直接照抄上一阶段的价值
那么如果能放得下的话,那么还需要做比较到底要不要放入的问题
- 那么如果不放入,那么还是照抄上一个阶段的价值
- 如果放入的话,那么就这样考虑:放入了以后,i的数量加了1,而背包中物体的总重量增加了$w_i$ ,所以,只要考虑$c[i-1][j-w[i]]$这一格的价值,再加上 我们加进去的物体的价值,就得到了我们新的价值。
- 如果这个物体很重但是价值不高,那应该是是不放价值高
1 | for(i = 1,i<=n,i++) |
算法分析:
时间复杂度:算法中有主要的两层嵌套的for循环,时间复杂度为$O(n*w)$
空间复杂度:算法中有二维数组$c[n][w]$,所以空间复杂度为$O(n*w)$
源码一览:
1 |
|
逆向构造最优解的图解
最大价值是17,那么我们先$C[5][10]$和$C[4][10]$相比,17>15 那么X[5]=1,j=j-w[i]=10-3=7
$C[4][7]$和$C[3][7]$比较,相等,说明第四个物品没有放进去
$C[3][7]$和$C[2][7]$比较,11>9,说明X[3]=1,j=j-w[i]=7-4=3
$C[2][3]$和$C[1][3]$比较,相等,说明第二个物品没有放进去
$C[1][3]$和$C[0][3]$比较,6>0,说明X[1]=1,比较结束
所以输出的时候,从1开始到n输出,放入次序为:1号,3号,5号
算法优化:
如果碰到背包数值w太大了,但是n又比较小的时候,从1开始遍历就需要很长时间
如果w过于大,那么就考虑回溯法,用一维数组求解。
既然只需要当前列和前面列值,那么只用一个一维数组倒推就可以了
用dp[j]表示物品放入容量为j的背包可以获得的最大价值
倒退时,因为从后往前推,前面的值还未更新,仍然未第i-1阶段的结果,意味着总是用第i-1段向第i阶段进行状态转移。第i-1阶段的结果,不包括第i个物品,这样保证第i个物品最多只会放入背包一次
如果是倒推保证第i个物品最多只会放入背包一次
如果是正推,那么我们看到他是拿了一个更新值去和原来的方法相比。4这一个数值加了两次
算法分析:
时间复杂度:算法中有主要的是两层嵌套的for循环,其时间复杂度是$O(n*w)$
空间复杂度:算法中使用了一维数组$dp[w]$,其时间复杂度是$O(n*w)$
1 | void opt2(int n ,int w) |
源码一览:
1 |
|
完全背包:
问题描述:
给定n个物品,每个物品有重量$w_i$和$v_i$ ,每种物品的数量没有限制。背包容量为W,要求在不超过背包容量的情况下,如何放置物品,让背包中的价值之和最大。可以对每个物品依次检查是否放入或者不放入,对第i个物品的处理状态:
用$c[i][j]$表示前i件物品放入一个容量为j的背包,可以获得的最大价值
- 如果尚未放入第i个物品,装入背包的价值不增加。那么问题就转化为’’前i-1件物品放入容量为j的背包中’’,最大值为$c[i-1][j]$
- 如果从第i件物品选择一个放入,因为第i件物品可以多次放入,相当于从第i-1阶段向第i阶段转化。问题就转化为“ 前i-1件物品放入容量为j-w[i]的背包中”,此时获得的最大价值就是$c[i][j-w[i]]$,再加上放入第i件物品获得的价值v[i].即$c[i][j-w[i]]+v[i]$
也可以优化为一维数组dp[j],然后采用正向循环,这样保证每个物品可以放入多次,用dp[j]表示物品放入一个容量为j的背包可以获得的最大价值
完全背包问题每种物品有无限个,可以多次放入,因此采用正推的形式求解。只需要讲01背包的代码的第二个for语句改成从前向后正向循环即可。、
1 | void comp_knapsack(int n ,int w) |
代码一览
1 |
|
多重背包
问题描述:
给定n个物品,每个物品中有重量$w_i$和价值$v_i$,每种物品的数量可以大于1但是有限制,第i个物品有$c_i$个,背包容量为W,要求在不超过背包容量的情况下,如何放置物品,让背包中物品的价值之和最大
1.暴力拆分
暴力拆分就是把第i种物品看作是$c_i$个独立的物品,每个物品只有一个,转化为01背包问题。但这样很容易超时
2.二进制拆分
当物品满足c[i]*w[i]>=W时,可以认为这种物品时不限数量的,按照完全背包的求解方法即可,否则,可以采用二进制拆分,把c[i]个物品拆分成若干物品怎么拆分呢?
首先$2^0+2^1+2^2…2^p<=C_i$找到这个p,然后把$C_i$分解成$2^0, 2^1,..2^p,C_i-(2^0, 2^1,..2^p)$一共p+2个,如果刚刚好等于的话那就是p+1个
比如说 $c_i = 9$那么也就是说分成1,2,4,2,四个物体,这样我们其实可以在这四个里取1-9中的任何数字,达到了拆分的效果
3.数组优化
用一个$num[j]$数组记录容量为j时,放入了多少个第 $i$ 个物品,由此实现数量限制约束
代码一览
1 |
|
分组背包
问题描述
给定n组物品,第i组有ci 个物品,第i组第j个物品有重量$w{ij}$和价值$v_{ij}$,背包容量为W,要求再不超过背包容量的情况下,每组至多选择一个物品,如何放置物品,让背包中物品价值之和最大。
因为每组之多选择一个物品,可以将每组看作一个整体,然后转换成01背包
算法分析:
用$C[i][j]$ 表示前i组物品放入一个容量为j的背包可以获得的最大价值,对于第i组物品的处理状态:
- 不放入第i组的物品,装入背包的价值不增加。那么问题就转化为“前i-1组物品放入容量为j的背包当中”,最大价值时$C[i][j]$
- 放入第i组的第k个物品,相当于从第i-1阶段向第i阶段转化。问题就转化为“前i-1组物品放入容量为$j-[i][k]$ 的背包中“,此时获得的最大价值就是$c[i-1][j-w[i][k]]+v[i][k]$
需要注意的是,枚举组内各物品的k一定在最内层循环,如果放在j的外层,那么则变为多重背包的暴力拆分算法,因为枚举放在j的外层.会出现组内多个物品多个放入的情况,这样就变成了多重背包
1 |
|
现在就可用动态规划的方法运用上面的公式来填表求解了.
1 |
|