04动态规划进阶---背包问题

来源:互联网 发布:gre词汇 知乎 编辑:程序博客网 时间:2024/06/11 10:54

背包问题可能是动态规划算法中最经典的问题了,三种最常见的背包问题分别是0-1背包问题,完全背包问题和多重背包问题。关于这三种背包的讲解网上有很多,但是很多只是给出状态转移方程或写出伪代码,或者是只给出0-1背包和完全背包的代码但并没有多重背包的代码,因此本文在这里将系统地总结这三种背包问题的最佳解法及相应java代码


题1.经典的0-1背包问题。给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高。比如说,现在有一个背包,它一共能装50的物品,现在有三种物品,重量分别10,20,30,价值分别为60,100,120,每种物品最多选一次,问如何选可以在不超过背包容量的情况下获取的价值最大。


首先分析本题的状态转移方程,设dp[i][j]表示取到第i件物品,背包容量为j时,背包所装物品的价值,对于每一件物品,要不选要么不选,则

dp[i][j]=max{dp[i-1][j],dp[i-1][j-w[i]]+v[i]}.

根据上述状态转移方程,可写出最基本的执行代码如下:

public class DP4 {/** * @param wj *///0-1背包问题,非压缩矩阵public static int func1(int capacity,int n,int[] w,int[] v){if(n==1) return v[0];int [][] dp=new int[n][capacity+1];//注意dp列长度为capacity+1for(int i=0;i<n;i++){for(int j=w[i];j<=capacity;j++){if(i>=1){//防止dp数组下标越界dp[i][j]=Math.max(dp[i-1][j], dp[i-1][j-w[i]]+v[i]);}else dp[i][j]=v[i];}}return dp[n-1][capacity];}public static void main(String[] args) {// TODO Auto-generated method stubint capacity=50;int [] w={10,20,30};int [] v={60,100,120};int n=3;//物品数量System.out.println(func1(capacity,n, w, v));//220}}
代码执行结果为220.


可以看到上述方法使用了一个二维数组来解决问题,虽然最直观最容易理解,但空间复杂度比较高。于是考虑能否将二维数组压缩为一维数组来解决问题。幸运地是,通过逆序方式,可以将二维数组压缩为一维数组,读者可以手动模拟一下算法执行过程,可以看到当将j逆序循环时,后一个状态值恰好等于前一个状态的执行结果,状态转移方程为dp[j]=max{dp[j],dp[j-w[i]]+v[i]},相当于省去了i之后,等号右边的dp[j]和dp[j-w[i]]+v[i]依然相当于dp[i-1][j]和dp[i-1][j-w[i]]+v[i]。有疑惑的话可以手动模拟算法过程看看,笔者曾手动模拟过,觉得这种方法确实很神奇。相应代码如下: 

public class DP4 {/** * @param wj *///0-1背包问题,压缩矩阵public static int func2(int capacity,int n,int[] w,int[] v){int [] dp=new int[capacity+1];//注意dp列长度为capacity+1for(int i=0;i<n;i++){for(int j=capacity;j>=w[i];j--){dp[j]=Math.max(dp[j], dp[j-w[i]]+v[i]);}}return dp[capacity];}public static void main(String[] args) {// TODO Auto-generated method stubint capacity=50;int [] w={10,20,30};int [] v={60,100,120};int n=3;System.out.println(func2(capacity,n, w, v));//220}}

代码执行结果为220。

可以看到上述方法将二维数组压缩为一维,空间复杂度大大降低。


题2.完全背包问题。有了0-1背包的基础,现在来看看完全背包问题。所谓完全背包就是在0-1背包的基础上,每件物品都有无数件,且每件物品可以取多件,求能装入背包的最大价值。


本题的状态转移方程为:dp[i][j]=max{dp[i-1][j],dp[i-1][j-k*w[i]]+k*v[i]},其中k<=capacity/w[i]。如果用这个转移方程来求解是比较麻烦,我们同样可以考虑压缩矩阵,压缩之后的状态转移方程为:dp[j]=max{dp[j],dp[j-w[i]]+v[i]},注意这里的j不再是逆序而是顺序,这是与0-1背包代码的唯一区别,有疑惑的可以手动模拟计算过程,相应代码如下:

public class DP4 {/** * @param wj *///完全背包问题,压缩矩阵public static int func3(int capacity,int n,int[] w,int[] v){int[] dp=new int[capacity+1];//注意dp列长度为capacity+1for(int i=0;i<n;i++){for(int j=w[i];j<=capacity;j++){dp[j]=Math.max(dp[j], dp[j-w[i]]+v[i]);}}return dp[capacity];}public static void main(String[] args) {// TODO Auto-generated method stubint capacity=50;int [] w={10,20,30};int [] v={60,100,120};int n=3;System.out.println(func3(capacity,n, w, v));//300}}

代码执行结果为300.即第一件物品取了5件。


题3.多重背包问题。本题是完全背包的变形,即现在每种物品有num[i]件,每种物品可取多件但不能超过num[i]件,求能装入背包的最大价值。


本题的状态转移方程与完全背包相似,dp[i][j]=max{dp[i-1][j],dp[i-1][j-k*w[i]]+k*v[i]},其中k<=num[i].我们依然采用压缩矩阵的方法,得出一维矩阵下的状态转移方程为:

dp[j]=max{dp[j],dp[j-w[i]]+v[i]},需要注意的是,这里的j需要逆序,而且需要多一个循环用来存放第i个物品的数量num[i],代码如下:

public class DP4 {/** * @param wj *///多重背包问题,压缩矩阵public static int func4(int capacity,int n,int[] w,int[] v,int[] num){int[] dp=new int[capacity+1];//注意dp列长度为capacity+1for(int i=0;i<n;i++){for(int j=0;j<num[i];j++){for(int k=capacity;k>=w[i];k--){dp[k]=Math.max(dp[k], dp[k-w[i]]+v[i]);}}}return dp[capacity];}public static void main(String[] args) {// TODO Auto-generated method stubint capacity=50;int [] w={10,20,30};int [] v={60,100,120};int [] num={3,4,5};//多重背包问题,各物品数量int n=3;System.out.println(func4(capacity, n, w, v, num));//280}}
代码的执行结果为:280,相当于第一件物品取3件,第二件物品取1件。

以上就是最常见的关于背包问题的分析与解法,希望对读者有一定帮助,有没解释清楚的地方大家也可以百度看看大牛们对背包问题,尤其是压缩矩阵的理解,笔者才疏学浅,要多向大佬们学习。



原创粉丝点击