参考《挑战程序设计竞赛》p51
01背包问题
- 问题描述:有n个重量和价值分别为wi、vi的物品,从这些物品中挑选出总重量不超过W的物品,求所有挑选方案中价值总和的最大值。
input:
4
5
2 3
1 2
3 4
2 2
output:
7(选择第0、1、 3号物品)
朴素解法:
c++版:
#include <iostream> using namespace std; int n,W; int *w,*v;//数组的指针 int max(int x, int y) { if (x>y) return x; return y; } int rec(int i, int j)//从数组下标为i的物品开始往后挑选总重小于j的物体,i从0开始 { int res; if (i==n) res=0;//没有物品了 else if (j<w[i]) res=rec(i+1,j);//重量j小于该组物品的重量,不能取 else res=max(rec(i+1,j),rec(i+1,j-w[i])+v[i]);//重量j大于该组物品的重量,能取;挑选和不挑选都尝试一下 return res; } int main() { cin >> n >> W;//n组物品,W:总重量 w = new int[n]; v = new int[n]; for (int i=0; i<n; i++) cin >> w[i] >> v[i]; cout << rec(0,W) << endl; }
Java版本
package 记忆化搜索; import java.util.Scanner; public class Main { static int[] w, v; public static void main(String[] args) { Scanner sc=new Scanner(System.in); int n=sc.nextInt(); int W=sc.nextInt(); w = new int[n]; v = new int[n]; for (int i=0; i<n; i++) { w[i]=sc.nextInt(); v[i]=sc.nextInt(); } System.out.println(rec(0,W)); } private static int rec(int i, int j) { if (i==w.length) { return 0; } if (j<w[i]) { return rec(i+1, j); } int a=rec(i+1, j); int b=rec(i+1, j-w[i])+v[i]; return Math.max(a, b); } }
这种方法的搜索深度是n,而且每一层的搜索都需要两次分支,最坏就需要O(2n)的时间。当n比较大时就没办法解了。所以要怎么办才好呢?为了优化之前的算法,我们看一下针对样例输人的情形下rec递归调用的情况。以下是rec(i,j)的模拟情况,i:第几组物品,j:重量
如图所示,rec以(3,2)为 参数调用了两次。如果参数相同,返回的结果也应该相同,于是第二次调用时已经知道了结果却白白浪费了计算时间。让我们在这里把第1次计算时的结果记录下来,省略掉第二次以后的重复计算试试看。
这微小的改进能降低多少复杂度呢?对于同样的参数,只会在第一次被调用到时执行递归部分,第二次之后都会直接返回。参数的组合不过nW种,而函数内只调用2次递归,所以只需要O(nW)的复杂度就能解决这个问题。只需略微改良,可解的问题的规模就可以大幅提高。这种方法一般被称为记忆化搜索。
c++版本:
#include <iostream> #include <cstring> using namespace std; int n,W; int *w,*v; int **dp; int max(int x, int y) { if (x>y) return x; return y; } int rec(int i, int j)//从数组下标为i的物品开始往后挑选总重小于j的物体 { if (dp[i][j]>=0) return j[i[dp]];//和dp[i][j]的意义一样 int res; if (i==n) res=0; else if (j<w[i]) res=rec(i+1,j); else res=max(rec(i+1,j),rec(i+1,j-w[i])+v[i]); dp[i][j] = res; return res; } int main() { cin >> n >> W; w = new int[n]; v = new int[n]; dp = new int*[n+1]; for (int i=0; i<=n; i++) { dp[i] = new int[W+1]; memset(dp[i],-1,sizeof(int)*(W+1)); } for (int i=0; i<n; i++) cin >> w[i] >> v[i]; cout << rec(0,W) << endl; }
Java版本:
package 记忆化搜索; import java.util.Arrays; import java.util.Scanner; public class Main { static int[] w, v; static int[][] dp; public static void main(String[] args) { Scanner sc=new Scanner(System.in); int n=sc.nextInt(); int W=sc.nextInt(); w = new int[n]; v = new int[n]; for (int i=0; i<n; i++) { w[i]=sc.nextInt(); v[i]=sc.nextInt(); } dp=new int[n+1][W+1]; for (int i = 0; i < dp.length; i++) { Arrays.fill(dp[i], -1); } System.out.println(rec(0,W)); } private static int rec(int i, int j) { if (dp[i][j]>=0) { return dp[i][j]; } if (i==w.length) { return 0; } if (j<w[i]) { return rec(i+1, j); } int a=rec(i+1, j); int b=rec(i+1, j-w[i])+v[i]; int res=Math.max(a, b); dp[i][j]=res; return res; } }
接下来,我们来仔细研究一下前面的算法利用到的这个记忆化数组。记dp[i][j]为 根据rec的定义,从第i个物品开始挑选总重小于j时,总价值的最大值。于是我们有如下递推式 :
dp[i+1][j] := 从前i+1个物品(即从编号为0到i这i+1个物品)中选出总重量不超过j的物品时总价值的最大值
dp[0][j] = 0
/ dp[i][j] (j<w[i]时)
dp[i+1][j] =
\ max(dp[i][j],dp[i][j-w[i]]+v[i]) (其它情况下)
如上所示,不同写递归函数,直接利用递推式将各项的值计算出来,简单地用二重循环也可以解决这一问题,复杂度为O(nW),与记忆化搜索是一样的,但是简洁了很多,这种方法叫做动态规划,即常说的DP:
c++版本解法:
#include <iostream> #include <cstring> using namespace std; int n,W; int *w,*v; int **dp; int max(int x, int y) { if (x>y) return x; return y; } int main() { cin >> n >> W; w = new int[n]; v = new int[n]; dp = new int*[n+1]; for (int i=0; i<=n; i++) { dp[i] = new int[W+1]; memset(dp[i],0,sizeof(int)*(W+1)); } for (int i=0; i<n; i++) cin >> w[i] >> v[i]; for (int i=0; i<n; i++) { for (int j=0; j<=W; j++) { if (j<w[i]) dp[i+1][j]=dp[i][j]; else dp[i+1][j] = max(dp[i][j],dp[i][j-w[i]]+v[i]); } } cout << dp[n][W] << endl; }
java版本:
参考代码:https://www.acwing.com/problem/content/submission/code_detail/3617/
import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner scanner=new Scanner(System.in); int n=scanner.nextInt(); int m=scanner.nextInt(); int v[]=new int[n+1]; int w[]=new int[n+1]; for (int i = 0; i <n; i++) { v[i]=scanner.nextInt(); w[i]=scanner.nextInt(); } int f[][]=new int[n+1][m+1]; for (int i = 0; i <n ; i++) { for (int j = 0; j <=m ; j++) { if(j<v[i]) f[i+1][j]=f[i][j]; else f[i+1][j]=Math.max(f[i][j],f[i][j-v[i]]+w[i]); } } System.out.println(f[n][m]); } }
原文地址:https://www.cnblogs.com/clarencezzh/p/10368986.html