小偷有一个容量为W的背包,有n件物品,第i个物品价值vi,且重wi
目标: 找到xi使得对于所有的xi = {0, 1}
sum(wi*xi) <= W, 并且 sum(xi*vi)最大
递归代码
package org.fan.learn.dp;
/**
* Created by fan on 2016/9/13.
*/
public class Bag {
public static int[][] result;
public static final int MAX_INT = 1000000;
public static final int BAG_MAX_WEIGHT = 12;
public static final int PRODUCT_COUNTS = 5;
// public static final int[] WEIGHTS = {1,9,3};
// public static final int[] VALUES = {6,2,3};
// public static final int[] WEIGHTS = {3,4,5};
// public static final int[] VALUES = {3,4,6};
public static final int[] WEIGHTS = {2, 2, 6, 5, 4};
public static final int[] VALUES = {6, 3, 5, 4, 6};
//这个返回的是有idx个物品时的最大价值,w表示这时书包还可以容纳的物品的重量
public static int search(int idx, int w) {
if (idx < 0) {
return 0;
}
if (WEIGHTS[idx] > w) {
return search(idx - 1, w);
}
//search(idx-1, w)表示没有选idx
//search(idx-1,w-WEIGHTS[idx])表示选了idx
int a = search(idx - 1, w);
int b = search(idx - 1, w - WEIGHTS[idx]) + VALUES[idx];
return Math.max(a, b);
}
//这个返回的是有idx物品时的最大价值,w表示这时书包中的重量
public static int search2(int idx, int w) {
if (idx >= PRODUCT_COUNTS) {
return 0;
}
int a = 0, b = 0;
//search(idx+1, w)表示没有选idx
//search(idx+1,w+WEIGHTS[idx])表示选了idx
if (w + WEIGHTS[idx] <= BAG_MAX_WEIGHT) {
a = search2(idx + 1, w + WEIGHTS[idx]) + VALUES[idx];
}
b = search2(idx + 1, w);
return Math.max(a, b);
}
//推荐这个写法
//这个返回的是有idx个物品时的最大价值,w表示这时书包还可以容纳的物品的重量
public static int search3(int idx, int w) {
if (idx < 0) {
return 0;
}
//result[idx][w]表示从前idx(包含idx)个东西,达到重量上限w,可以获得的最大价值
if (result[idx][w] >= 0) {
return result[idx][w];
}
//冗余的地方,主要是因为有两个search。而n!中是没有冗余的
//(idx-1, w-W[idx])
//(idx-1, w-W[idx])
int a = 0, b = 0;
//search(idx-1, w)表示没有选idx
//search(idx-1,w-WEIGHTS[idx])表示选了idx
if (WEIGHTS[idx] <= w) {
a = search3(idx - 1, w - WEIGHTS[idx]) + VALUES[idx];;
}
b = search3(idx - 1, w);
/*
上面的代码不能这么写:
if (WEIGHTS[idx] > w) {
a = search3(idx - 1, w);
}
b = search3(idx - 1, w - WEIGHTS[idx]) + VALUES[idx];
这么写只有WEIGHTS[idx] > w时才会调用search3(idx - 1, w),
其实当WEIGHTS[idx] < w时,也可以调用search3(idx - 1, w)
*/
result[idx][w] = Math.max(a, b);
return result[idx][w];
}
public static void main(String[] args) {
result = new int[PRODUCT_COUNTS][BAG_MAX_WEIGHT+1];
for (int i = 0; i < PRODUCT_COUNTS; i++) {
for (int j = 0; j < BAG_MAX_WEIGHT+1; j++) {
result[i][j] = -1;
}
}
System.out.println(search(PRODUCT_COUNTS-1, BAG_MAX_WEIGHT));
System.out.println(search2(0, 0));
System.out.println(search3(PRODUCT_COUNTS - 1, BAG_MAX_WEIGHT));
}
}
执行结果:
17
17
17
递推代码
//这是result[i][j]的含义:前i个东西,获得w重量时的最大价值
public static int searchDitui(int idx, int w) {
result[0][0] = 0;
for (int i = 1; i <= BAG_MAX_WEIGHT; i++) {
if (WEIGHTS[0] > i) {
//当前0号东西的重量已经超过目标重量j,因此不能取0号东西,获得的价值为0
result[0][i] = 0;
} else {
//WEIGHTS[0] <= i此时可以拿0号东西,获得0号东西的价值
result[0][i] = VALUES[0];
}
}
for (int i = 1; i < PRODUCT_COUNTS; i++) {
result[i][0] = 0;
for (int j = 1; j <= BAG_MAX_WEIGHT; j++) {
//不取i号东西,因此跟前一个东西的价值相同
result[i][j] = result[i-1][j];
//只有当i号东西的重量小于等于目标重量j时才取i号东西
if (WEIGHTS[i] <= j) {
result[i][j] = Math.max(result[i-1][j-WEIGHTS[i]]+VALUES[i], result[i][j]);
}
}
}
return result[idx][w];
}
递推优化
使用滚动数组。
//这是result[i][j]的含义:前i个东西,获得w重量时的最大价值
public static int searchDituiYouhua(int idx, int w) {
result[0][0] = 0;
for (int i = 1; i <= BAG_MAX_WEIGHT; i++) {
if (WEIGHTS[0] > i) {
//当前0号东西的重量已经超过目标重量j,因此不能取0号东西,获得的价值为0
result[0][i] = 0;
} else {
//WEIGHTS[0] <= i此时可以拿0号东西,获得0号东西的价值
result[0][i] = VALUES[0];
}
}
for (int i = 1; i < PRODUCT_COUNTS; i++) {
result[i%2][0] = 0;
for (int j = 1; j <= BAG_MAX_WEIGHT; j++) {
//不取i号东西,因此跟前一个东西的价值相同
result[i%2][j] = result[(i-1)%2][j];
//只有当i号东西的重量小于等于目标重量j时才取i号东西
if (WEIGHTS[i] <= j) {
result[i%2][j] = Math.max(result[(i-1)%2][j-WEIGHTS[i]]+VALUES[i], result[i%2][j]);
}
}
}
return result[idx%2][w];
}
完整代码
package org.fan.learn.dp;
/**
* Created by fan on 2016/9/13.
*/
public class Bag {
public static int[][] result;
public static final int MAX_INT = 1000000;
public static final int BAG_MAX_WEIGHT = 10;
public static final int PRODUCT_COUNTS = 3;
// public static final int[] WEIGHTS = {1,9,3};
// public static final int[] VALUES = {6,2,3};
public static final int[] WEIGHTS = {3,4,5};
public static final int[] VALUES = {3,4,6};
// public static final int[] WEIGHTS = {2, 2, 6, 5, 4};
// public static final int[] VALUES = {6, 3, 5, 4, 6};
//这个返回的是有idx个物品时的最大价值,w表示这时书包还可以容纳的物品的重量
public static int search(int idx, int w) {
if (idx < 0) {
return 0;
}
if (WEIGHTS[idx] > w) {
return search(idx - 1, w);
}
//search(idx-1, w)表示没有选idx
//search(idx-1,w-WEIGHTS[idx])表示选了idx
int a = search(idx - 1, w);
int b = search(idx - 1, w - WEIGHTS[idx]) + VALUES[idx];
return Math.max(a, b);
}
//这个返回的是有idx物品时的最大价值,w表示这时书包中的重量
public static int search2(int idx, int w) {
if (idx >= PRODUCT_COUNTS) {
return 0;
}
int a = 0, b = 0;
//search(idx+1, w)表示没有选idx
//search(idx+1,w+WEIGHTS[idx])表示选了idx
if (w + WEIGHTS[idx] <= BAG_MAX_WEIGHT) {
a = search2(idx + 1, w + WEIGHTS[idx]) + VALUES[idx];
}
b = search2(idx + 1, w);
return Math.max(a, b);
}
//推荐这个写法
//这个返回的是有idx个物品时的最大价值,w表示这时书包还可以容纳的物品的重量
public static int search3(int idx, int w) {
if (idx < 0) {
return 0;
}
//result[idx][w]表示从前idx(包含idx)个东西,达到重量上限w,可以获得的最大价值
if (result[idx][w] >= 0) {
return result[idx][w];
}
//冗余的地方,主要是因为有两个search。而n!中是没有冗余的
//(idx-1, w-W[idx])
//(idx-1, w-W[idx])
int a = 0, b = 0;
//search(idx-1, w)表示没有选idx
//search(idx-1,w-WEIGHTS[idx])表示选了idx
if (WEIGHTS[idx] <= w) {
a = search3(idx - 1, w - WEIGHTS[idx]) + VALUES[idx];;
}
b = search3(idx - 1, w);
/*
上面的代码不能这么写:
if (WEIGHTS[idx] > w) {
a = search3(idx - 1, w);
}
b = search3(idx - 1, w - WEIGHTS[idx]) + VALUES[idx];
这么写只有WEIGHTS[idx] > w时才会调用search3(idx - 1, w),
其实当WEIGHTS[idx] < w时,也可以调用search3(idx - 1, w)
*/
result[idx][w] = Math.max(a, b);
return result[idx][w];
}
//这是result[i][j]的含义:前i个东西,获得w重量时的最大价值
public static int searchDitui(int idx, int w) {
result[0][0] = 0;
for (int i = 1; i <= BAG_MAX_WEIGHT; i++) {
if (WEIGHTS[0] > i) {
//当前0号东西的重量已经超过目标重量j,因此不能取0号东西,获得的价值为0
result[0][i] = 0;
} else {
//WEIGHTS[0] <= i此时可以拿0号东西,获得0号东西的价值
result[0][i] = VALUES[0];
}
}
for (int i = 1; i < PRODUCT_COUNTS; i++) {
result[i][0] = 0;
for (int j = 1; j <= BAG_MAX_WEIGHT; j++) {
//不取i号东西,因此跟前一个东西的价值相同
result[i][j] = result[i-1][j];
//只有当i号东西的重量小于等于目标重量j时才取i号东西
if (WEIGHTS[i] <= j) {
result[i][j] = Math.max(result[i-1][j-WEIGHTS[i]]+VALUES[i], result[i][j]);
}
}
}
return result[idx][w];
}
public static void main(String[] args) {
result = new int[PRODUCT_COUNTS][BAG_MAX_WEIGHT+1];
for (int i = 0; i < PRODUCT_COUNTS; i++) {
for (int j = 0; j < BAG_MAX_WEIGHT+1; j++) {
result[i][j] = -1;
}
}
System.out.println(search(PRODUCT_COUNTS-1, BAG_MAX_WEIGHT));
System.out.println(search2(0, 0));
System.out.println(search3(PRODUCT_COUNTS - 1, BAG_MAX_WEIGHT));
System.out.println(searchDitui(PRODUCT_COUNTS - 1, BAG_MAX_WEIGHT));
}
}
运行结果
10
10
10
10