问题:给大小为n的数组weights[]和values[], 求在不超过W的情况下能取得的value总和最大是多少,每一个数只能最多取一次(或者不取)
本质是排列组合,复杂度O(2^n)
public static void main(String[] ss) { int[] a = {1,2,3}; int[] b = {4,2,1}; int w = 3; System.out.println(knap(a, b, w)); } private static int maxValue = 0; private static int knap(int weights[], int values[], int maxW) { dfs(weights, values, 0, 0, 0, maxW); return maxValue; } private static void dfs(int[] weights, int[] values, int curV, int curW, int index, int maxWeight) { if (curW > maxWeight) { return; } maxValue = Math.max(maxValue, curV); for (int i = index; i < weights.length; i++) { dfs(weights, values, curV + values[i], curW + weights[i], i + 1, maxWeight); } }
另一种写法。这种写法更容易看出我们可以用[index, curW] = weight来标记每一个状态(递归树上的结点)
O(2^n)
private static int knap(int weights[], int values[], int maxW) { return dfs(weights, values, 0, 0, maxW); } private static int dfs(int[] weights, int[] values, int curW, int index, int maxWeight) { if (index == weights.length) { return 0; } int max_1 = 0; if (curW + weights[index] <= maxWeight) { max_1 = dfs(weights, values, curW + weights[index], index + 1, maxWeight) + values[index]; } int max_2 = dfs(weights, values, curW, index + 1, maxWeight); return Math.max(max_1, max_2); }
Memoization
O(maxW * n)
private static int knap(int weights[], int values[], int maxW) { int n = weights.length; int[][] dp = new int[n][maxW + 1]; return dfs(weights, values, 0, 0, maxW, dp); } private static int dfs(int[] weights, int[] values, int curW, int index, int maxWeight, int[][] dp) { if (index == weights.length) { return 0; } if (dp[index][curW] != 0) { return dp[index][curW]; } int max_1 = 0; if (curW + weights[index] <= maxWeight) { max_1 = dfs(weights, values, curW + weights[index], index + 1, maxWeight, dp) + values[index]; } int max_2 = dfs(weights, values, curW, index + 1, maxWeight, dp); int rt = Math.max(max_1, max_2); dp[index][curW] = rt; return rt; }
iterative dp, 通项公式跟递归是一摸一样
O(maxW * n)
private static int knap(int weights[], int values[], int maxW) { int n = weights.length; int[][] dp = new int[n][maxW + 1]; for (int i = 0; i < maxW + 1; i++) { if (weights[0] <= i) { dp[0][i] = values[0]; } } for (int i = 1; i < n; i ++) { for (int j = 1; j < maxW + 1; j++) { if (j - weights[i] >= 0) { dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weights[i]] + values[i]); }else { dp[i][j] = dp[i - 1][j]; } } } return dp[n - 1][maxW]; }可以发现当前的dp[row]只依赖于之前一行dp[row - 1], 所以可以简化为一维dp
private static int knap(int weights[], int values[], int maxW) { int n = weights.length; int[] dp1 = new int[maxW + 1]; int[] dp2 = new int[maxW + 1]; for (int i = 0; i < maxW + 1; i++) { if (weights[0] <= i) { dp1[i] = values[0]; } } for (int i = 1; i < n; i ++) { for (int j = 1; j < maxW + 1; j++) { if (j - weights[i] >= 0) { dp2[j] = Math.max(dp1[j], dp1[j - weights[i]] + values[i]); }else { dp2[j] = dp1[j]; } } int[] tmp = dp1; dp1 = dp2; dp2 = tmp; Arrays.fill(dp2, 0); } return dp1[maxW]; }
Unbounded knapsack
如果按0-1的解法的话会形成3个嵌套的for循环
换一种思路
, ,
O(n * maxW)
private static int knap(int weights[], int values[], int maxW) { int n = weights.length; int[] dp = new int[maxW + 1]; for (int i = 1; i < maxW + 1; i++) { for (int j = 0; j < n; j++) { if (i >= weights[j]) { dp[i] = Math.max(dp[i], dp[i - weights[j]] + values[j]); } } } return dp[maxW]; }
ToDo
可以用贪心算法吗?v / w 最大的那个一直加
Bounded knapsack
No comments:
Post a Comment