问题描述:
有 n 个物品, 第 i 种物品的价值为 \(p_i\)重量为 \(W_i\), 选一些物品到一个容量为 C 的背包里, 使得背包内物品在总重量不超过 C 的前提下, 价值尽量大.
问题分析:
? 在之前我们了解贪心思想的时候曾经有过类似的题目那时候物品是可拆分的我们只需要选择单位重量最大的物品即可. 但是在这里, 每一个物品都是完整的, 只能是装或者不装. 我们分析, 有 n 个物品, 为 \(a_1\)...\(a_n\)当对 \(a_1\)做决策之后,\(a_2\)...\(a_n\)是类似的问题. 发现它可以用递归去解, 那样的话, 可以用 DP 去求解吗. 如果可以的话我们就需要找到他的决策或者它的子问题.
? 针对 \(a_1\), 我们发现当 \(W_1\)>C 时, 说明 \(a_1\)无法装进背包, 问题变为求 \(a_2\)...\(a_n\)装进容量为 C 的背包求最大价值. 当 \(W_1\)<C, 可以选择装入背包, 则问题变为求 \(a_2\)...\(a_n\)装进容量为 C-\(W_1\)的背包, 求最大价值这时候价值变为 \(P_1\)+ 所求. 另一种选择是不装入背包这时候问题变为求 \(a_2\)...\(a_n\)装进容量为 C 的背包求最大价值. 对于父问题 (也就是这个问题) 来说, 最优值在这两者之间取较大值. 然后把这个最优值记录下来. 接下来的每一个都是这样的操作(子问题). 最终递归出口就是只剩最后一个, an...an, 当 \(W_n\)>C 时不能装最大的价值就是 0,\(W_n\)<=C 时, 最大价值就是 \(P_n\)
问题求解
设 dp[i][j]表示从 \(a_i\)到 \(a_n\)的最大价值. j 为背包的剩余容量. 所以 j 这里我们默认为了整数, 潜在的,\(W_i\)应该也是整数.(思考: 当这里不是整数的话应该怎么办?)
- ?
- \(dp[i][j]=\left\{
- \begin{
- aligned
- } dp[i+1][j],{
- W_i>C
- }\max{\left\{
- \begin{
- aligned
- }dp[i+1][j], 不装,{
- W_i<=C
- }\dp[i+1][j-{
- W_i
- }]+{
- P_i
- }, 装,{
- W_i<=C
- }\end{
- aligned
- }\right.
- } \end{
- aligned
- } \right. \)
- ?
递归出口(函数初值):
\(dp[n][j]=\left\{ \begin{aligned} 0,{W_i>C}\{P_i},{W_i<C} \end{aligned} \right. \)
Java 代码实现
- /**
- * DP 问题处理 01 背包问题
- * @param w 物品重量
- * @param p 物品价值
- * @param C 背包容量
- * @param n 物品个数
- */
- public static void test(int[] w,int[] p,int C,int n) {
- int[][]dp=new int[n+1][C+1];
- // 初值
- for(int i=0;i<=C;i++)
- if(i>=w[n])
- dp[n][i]=p[n];
- //2~n-1
- for(int i=n-1;i>=2;i--) {
- for(int j=0;j<=C;j++) {
- if(w[i]>j)
- dp[i][j]=dp[i+1][j];
- else {
- int temp=dp[i+1][j-w[i]]+p[i];
- if(temp>dp[i+1][j])
- dp[i][j]=temp;
- else
- dp[i][j]=dp[i+1][j];
- }
- }
- }
- // 求 1,C
- if(w[1]>C)
- dp[1][C]=dp[2][C];
- else {
- int temp=dp[2][C-w[1]]+p[1];
- if(temp>dp[2][C])
- dp[1][C]=temp;
- else
- dp[1][C]=dp[2][C];
- }
- for(int i=0;i<dp.length;i++) {
- for(int j=0;j<dp[0].length;j++) {
- System.out.print(dp[i][j]+" ");
- }
- System.out.println();
- }
- }
最优解明天再写, 今天太晚了, 第一次用 Markdown 可把我累坏了
优化方向: 时间方面: 因为是 j 是整数是跳跃式的, 可以选择性的填表.
(DP 它避免了很多重复计算, 但有时候会计算无用的子问题就是做了许多无用计算. 可以以这种思想进行优化.)
总结
首先, 这个问题的解决从递归到递推, 因为 i(开始的物品)和 j(背包)的存在选用二维数组(i..n,n 是一定的, 所以只有两个参数).
其次, 我一开始想的是倒着推, 从只有一个物品到 n 个这也是 DP 问题常用的想法, 就是从小问题到大问题.
最后, DP 问题的下手点可以先分析出它的子问题, 但是这个子问题是来源与决策的. 所以, 决策也很重要.
来源: http://www.bubuko.com/infodetail-3518231.html