问题:给大小为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