1、01背包
内容:有n件物品和容量为m的背包 给出i件物品的重量以及价值 求解让装入背包的物品重量不超过背包容量 且价值最大 java
特色:每一个物品只有一件供你选择放仍是不放数组
1. 二维解法 优化
设f[i][j]表示前i件物品 总重量不超过j的最大价值 可得出状态转移方程 :.net
f[i][j]=max{f[i-1][j-w[i]]+v[i],f[i-1][j]} code
w[i]:重量数组,v[i]:价值数组blog
代码以下:rem
// 0,1背包:二维法 public static int bag1(int W, int[] w, int[] v) { int n = w.length - 1;// 第一个值,不算 int[][] f = new int[n + 1][W + 1]; for (int i = 1; i <= n; i++) for (int j = W; j >= w[i]; j--) f[i][j] = Math.max(f[i - 1][j], f[i - 1][j - w[i]] + v[i]); return f[n][W]; // 最优解 }
缺点:数据大时,没法处理。get
2.一维解法 博客
用一维数组代替二维数组就解决了二维数组的问题io
设f[j]表示重量不超过j公斤的最大价值 可得出状态转移方程 :
f[j]=max{f[j],f[j−w[i]]+v[i]}
代码以下:
// 0,1背包:一维法 public static int bag2(int W, int[] w, int[] v) { int n = w.length - 1;// 第一个值,不算 int[] f = new int[W + 1]; for (int i = 1; i <= n; i++) for (int j = W; j >= w[i]; j--) f[j] = Math.max(f[j], f[j - w[i]] + v[i]); return f[W]; // 最优解
注意:这里第二个for循环为何从大到小?
由于按正常二维解法,这个 f[i][j]=max{f[i-1][j-w[i]]+v[i],f[i-1][j]} 是要跟上一层i-1比较的,若是一维也用从小到大,不能保证上一层没有遍历过(即不包含vi,每一个只能用一次),这样就乱了,所以要从大到小顺序遍历。
2、彻底背包
内容:有n件物品和容量为m的背包 给出i件物品的重量以及价值 求解让装入背包的物品重量不超过背包容量 且价值最大
特色: 每一个物品能够无限选用
有二维和一维两种,但二维的有必定局限 因此只介绍一维方法
设f[j]表示重量不超过j公斤的最大价值 可得出状态转移方程 :
f[j]=max{f[j],f[j−w[i]]+v[i]}
区别:0,1背包是从大到小遍历,彻底背包是从小到大遍历
代码以下:
// 彻底背包:一维法-倒序 public static int bag3(int W, int[] w, int[] v) { int n = w.length - 1;// 第一个值,不算 int[] f = new int[W + 1]; for (int i = 1; i <= n; i++) for (int j = W; j >= w[i]; j--) for (int k = 0; j - k*w[i] >=0; k++) f[j] = Math.max(f[j], f[j - k*w[i]] +k* v[i]); return f[W]; // 最优解 }
优化: 转换成从小到大,由于每一个物品能够用无限次,只要不许超太重量,就不在意以前i(上一层)是否用过。
// 彻底背包:一维法-正序 public static int bag3(int W, int[] w, int[] v) { int n = w.length - 1;// 第一个值,不算 int[] f = new int[W + 1]; for (int i = 1; i <= n; i++) for (int j = w[i]; j <= W; j++) f[j] = Math.max(f[j], f[j - w[i]] + v[i]); return f[W]; // 最优解 }
3、多重背包
内容:有n件物品和容量为m的背包 给出i件物品的重量以及价值 还有数量 求解让装入背包的物品重量不超过背包容量 且价值最大
特色:每一个物品都有了必定的数量
设f[j]表示重量不超过j公斤的最大价值 可得出状态转移方程 :
f[j]=max{f[j],f[j−k∗w[i]]+k∗v[i]}
代码以下:
// 多重背包:一维法 public static int bag4(int W, int[] w, int[] v, int[] num) { int n = w.length - 1;// 第一个值,不算 int[] f = new int[W + 1]; for (int i = 1; i <= n; i++) for (int j = W; j >= w[i]; j--) for (int k = 0; k <= num[i] && j - k * w[i] >= 0; k++) { f[j] = Math.max(f[j], f[j - k * w[i]] + k * v[i]); } return f[W]; // 最优解 }
二进制优化作法:
首先,能够把它转化为一个01背包的问题。每一个物品有s件,咱们能够把它差分红s份,每份物品当作不一样的个体,即只能选一次,这就转化为了01背包物品,可是这样的话,复杂度仍是很大。
继续优化,一个物品的数量是s的话,只要把s拆分红一些数字,使它们可以表示出1-s中任意一个数字,就能够,不必把它拆成s个1。
那么这样的数字最少须要多少个呢?最少须要log(s)个,向上取整。
好比7,它最少须要3个数字来表示:
即 1(2^0=1 ), 2(2^1=2), 4(2^2=4)。缘由:每一个数字有2种可能选或不选,那么能够表示的不一样数字个数就是 2 * 2 * 2 = 8。可是还须要注意一个问题,就是有些数字可能可以表示出来一些大于s的数字,可是这件物品最多只有s件,那么就须要特殊处理一下最后一个数。
好比10,若用1,2, 4, 8表示,可能会表示出来大于10的数字,例如:4+8=12。那么若是最后一个数字加上前面数的总和会大于s,就将它替换为剩下的物品个数,即将8替换为3,这时正好能表示出1-s全部的数,-> 1, 2,4能够表示7之内的全部数,这些数加上3就能够表示10之内的全部数啦。
——参考背包九讲
// 多重背包:一维法-二进制优化 public static int bag4(int W, int[] w, int[] v, int[] num) { int n = w.length; int[] f = new int[W + 1]; int[] vv = new int[n*W]; int[] ww = new int[n*W]; // 复制w和v for(int i=0;i<n;i++) { vv[i] = v[i]; ww[i] = w[i]; } k = n; for(int i=0;i<n;i++) { s = num[i] for(int i=1;i<=s;i*=2) //二进制优化 vv[k]=v[i]*i,ww[k++]=w[i]*i,s-=i; if(s>0) vv[k]=v[i]*s,ww[k++]=w[i]*s; } for (int i = 1; i <= k; i++) //01背包 for (int j = W; j >= ww[i]; j--) f[j] = Math.max(f[j], f[j - ww[i]] + vv[i]); return f[W]; // 最优解 }
4、背包问题应用在,数组中找固定和的问题
1. 判断是否有和为100的组合
public static boolean findSum100(int[] a) { boolean[] dp = new boolean[101]; dp[a[0]] = true; for (int i = 1; i < a.length; i++) { for (int j = 100; j >= a[i]; j--) { if (dp[j - a[i]]) { dp[j] = true; } } if (dp[100]) { return true; } } return false; }
2.查找和为100的全部组合
方法一:DFS(2^n)
public static void dfs(int current, int sum, int[] questions, ArrayList<Integer> path, HashSet<List<Integer>> result) { if (sum == 100) { result.add(new ArrayList<>(path)); return; } if (sum > 100) { return; } for (int i = current; i < questions.length; i++) { path.add(questions[i]); dfs(i + 1, sum + questions[i], questions, path, result); path.remove(path.size() - 1); } }
方法二:动态规划 O(n^2)
// 打印和为n的组合,动规法,O(n^2) public static List<List<Object>> findSums(int[] a, int n) { boolean[] dp = new boolean[n + 1]; List<List<Object>>[] list = new ArrayList[n + 1]; for (int i = 0; i < list.length; i++) { list[i] = new ArrayList<>(); } List<Object> temp = new ArrayList<>(); for (int i = 0; i < a.length; i++) { if (a[i] > n) { continue; } for (int j = n; j >= a[i]; j--) { if (dp[j - a[i]]) { dp[j] = true; for (List<Object> arrayList : list[j - a[i]]) { temp = new ArrayList<>(arrayList); temp.add(a[i]); list[j].add(new ArrayList<>(temp)); } } } dp[a[i]] = true; temp.clear(); temp.add(a[i]); list[a[i]].add(new ArrayList<>(temp)); } return list[n]; }
更多打印的问题:
打印和为sum的组合,动规法+DFS+迭代法 - CSDN博客 :https://blog.csdn.net/qq_19446965/article/details/81775702