背包问题(0-1背包+彻底背包)

0-1背包数组

有N件物品和一个容量为V的背包。第i件物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可以使价值总和最大。

重要的点在于:每种物品仅有一件,能够选择放/不放网络

子问题:f[i][v]表示前i件物品刚好放入一个 容量为v 的背包能够得到的最大价值。优化

状态转移方程(递推式):f[i][v]=max{f[i-1][v], f[i-1][v-c[i]]+w[i]};//考虑前i件物品放入这个子问题的时候,能够转化为前i-1件物品已经放好。那么若是放入第i件物品,那么问题转化为 前i-1件物品放入剩余容量为v-c[i]的背包里;若是不放入第i件物品,那么问题转化为 前i-1件物品放入剩余容量为v的背包里。spa

而若是放入第i件物品,那么当前价值就是f[i-1][v-c[i]]+w[i]。所以当前最大价值就是 放入&不放入 之间的最大值。3d

 

能够反向找到各类物品的选择:从dp[N][V]开始,若是dp[i][j]=dp[i-1][j],则当前第i件物品没有被选中,从dp[i-1][j]继续找;不然,则表示选中,从dp[i-1][j-w[i]]开始找code

伪代码:blog

int[][] dp=new int[N][V+1];
//初始化第一行
//仅考虑容量为V的背包放第0个物品,不放物品,价值为0
for(int i=0;i<=V;i++){
     dp[0][i]=0;  
}
//初始化第一列
//容量为0的背包放物品,不放物品,价值为0
for(int i=0;i<=N;i++){
dp[i][0]=0;
}
//根据状态转移方程,填充其余行和列 for(int i=1;i<N;i++){ for(int j=1;j<=V;j++){
//装不进去,当前剩余空间小于第i个物品的大小
if(w[i]>j){
           dp[i][j]=dp[i-1][j];
}
//容量足够,能够放进去,比价值更大的方法

else
{ dp[i][j]=Math.max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]); } } } //最后的结果 dp[N-1][V]

如何优化呢?那么只能在空间复杂度上进行优化,只使用一维数组来存放结果递归

此时状态转移方程为:f[v]=max{f[v], f[v-c[i]]};//那么这个时候须要注意的是,在第二层循环中,须要使用从后往前计算,获得结果。即要用一维数组记忆化的时候,须要用到当前位置的值 和 该位置以前的值。由于若是咱们须要计算f[4, 4]=max{f[3,4], f[3,1]}、f[4,3]=max{f[3,3], f[3,1]}、f[4,2]=max{f[3,2], f[3,1]}。class

若是是转化为一维数组,由于须要保证max中的f[v]是f[i-1][v],前面的f[v]是f[i][v]。也就是当前这一层的d[j]尚未被更新过,因此当前的d[j]用到的是i-1层的结果。若是从前日后计算,那么下一次使用的d[j]是本层已经更新过的,会覆盖掉i-1层的结果。循环

//解释

因为知道dp[i-1,1...j]就能够获得dp[i,j],下一层只须要根据上一层结果就能够推出答案。

对于dp[j]=max{dp[j], dp[j-w[i]]+v[i]}而言,dp[j-w[i]]至关于二维的dp[i-1][j-w[i]],dp[j]是由前面的dp(1...j)推出来的。

所以好比从i=3推i=4,此时一维数组存放{0,0,2,4,4,6,6,6,7,7,9},这是i=3时全部子问题的解。若是从前日后推,那么计算i=4时,

dp[0]=0, dp[1]=0, ... , (前面这几项都放不进 w[i]=5的物品)dp[5]=max{dp[5], dp[5-5]+7}=7, dp[6]=max{dp[6], dp[6-5]+7}=7, dp[7]=max{dp[7], dp[7-5]+7}=9.....这里会更新dp[5]、dp[6]...的值,那么后续计算的时候 就没办法用到 上一轮循环时的 dp[5]、dp[6]....了(即 由于当前值 是由上一轮循环推出来的,若是从前日后,前一次循环保存下来的值 可能会被修改)就是我当前更新要用到这个值,可是这个值 在从前日后更新时,已经被修改了,那么我用到的就是错误的值了。

 

初始化的话:(初始化 其实是 在没有任何物品能够放入背包时 的合法状态)

若是问法是“刚好装满”的最优解,那么除了dp[0]初始化为0,其余都应该设置为 负无穷大。这样能保证最终的dp[V]为刚好装满背包时的最优解。此时,只有容量为0的背包 能够在 什么都不装且价值为0时被“刚好装满”,由于如dp[3]则表示,背包容量为3时,刚好装满的价值,此时没有合法的解,所以属于未定义状态,设为无穷大。

若是问法是“能够不装满”的最优解,那么全部的都应初始化为0,由于“什么都不装”时,0就是合法解。

伪代码:

int[] dp=new int[V+1];
//初始化第一行
//仅考虑容量为V的背包放第0个物品,不放物品,价值为0
for(int i=0;i<=V;i++){
     dp[i]=w[0]<=i?v[0]:0;  
}

//根据状态转移方程,填充其余行和列
for(int i=1;i<N;i++){
    for(int j=V;j>=w[i];j--){
            dp[j]=Math.max(dp[j],dp[j-w[i]]+v[i]);

    }
}

//最后的结果
dp[V]

例题:给定一个仅包含正整数的非空数组,肯定该数组是否能够分红两部分,要求两部分的和相等

思路:即给定N个元素组成的数组arr,数组元素的和为sum。转换成背包问题,每一个物品的重量和价值为arr[i],两部分和相等,即背包的限重为sum/2.

if(nums==null || nums.length==0){
     return true;
}
int sum=0;
for(int num : nums){
     sum+=num;
}
//若是sum不能够平分,那么就不可分为两块
if(sum%2!=0){
     return false;
}
sum/=2;
//定义
boolean[] dp=new boolean[sum+1];
//初始化
dp[0]=true; for(int i=1; i<=nums.length; i++){
//为何要从后往前更新dp,由于每一个位置 依赖于 前面一个位置 加上 nums[i]。若是从前日后更新的话,
那么dp[i-2]会影响dp[i-1],而后dp[i-1]会影响dp[i],即一样的一个nums[i]被反复使用了屡次。
for(int j=sum; j>=nums[i]; j--){ dp[j]=dp[j] || dp[j-nums[i]]; } } //输出 dp[sum]

 

彻底背包

有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可以使这些物品的费用总和不超过背包容量,且价值总和最大。

重要的区别在于彻底背包是 每种无限件

状态转移方程:f[i][j]=Math.max(f[i-1][j-k*c[i]]+k*w[i]),0<=k*c[i]<=j;//根据第i件物品放多少件,即前i-1件物品中 选择 若干件 放入剩余的空间上,使得最大。(f[i][j]表示 前i种物品 放入一个容量为j的背包种得到 最大价值)

//递归和动态规划的区别:动态规划多使用了一个二维数组来存储中间的解

代码:

int[][] dp=new int[N][V+1];
//初始化第一行
//仅考虑容量为V的背包放第0个物品,不放物品,价值为0
for(int i=0;i<=V;i++){
     dp[0][i]=0;  
}
//初始化第一列
//容量为0的背包放物品,不放物品,价值为0
for(int i=0;i<=N;i++){
     dp[i][0]=0;
}
//根据状态转移方程,填充其余行和列
for(int i=1;i<N;i++){
    for(int j=1;j<=V;j++){
           //装不进去,当前剩余空间小于第i个物品的大小
           if(w[i]>j){
                dp[i][j]=dp[i-1][j];
           }
           //容量足够,能够放进去,比价值更大的方法。取k个物品i,再k种选择 选出 最优解
           else{
                 for(int k=0; k*w[i]<=j; k++){
                        dp[i][j]=Math.max(dp[i-1][j], dp[i-1][j-w[i]*k])+v[i]*k;
                 }                 
           }    
    }
}

//最后的结果
dp[N-1][V]

一样使用一维数组 来优化 空间复杂度

dp[i]=Math.max(dp[i], dp[i-w[i]]+v[i])

int[] dp=new int[V+1];
//初始化第一行
//仅考虑容量为V的背包放第0个物品,不放物品,价值为0
for(int i=0;i<=V;i++){
     dp[i]=0;  
}

//根据状态转移方程,填充其余行和列
for(int i=1;i<N;i++){
    for(int j=w[i];j<=V;j++){
            dp[j]=Math.max(dp[j],dp[j-w[i]]+v[i]);

    }
}

//最后的结果
dp[V]

(示意图源于网络,侵删)

相关文章
相关标签/搜索