背包问题的实现 * 1.全组合解法(对数器) * 2.暴力递归解法 * 3.动态规划解法 * 4.动态规划(省空间)解法
对数器的思路是:求解n个物品的全组合中不超过背包容量的组合对应的最大价值(时间复杂度极高: )
package com.logbug.algorithm.knapsack;
import lombok.Data;
import java.util.Arrays;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* 背包问题的实现
* 1.全组合解法(对数器)
* 2.暴力递归解法
* 3.动态规划解法
* 4.动态规划(省空间)解法
*
* @author : lin.chen1
* @version : 1.0.0.0
*/
@Data
public class KnapsackRecursion {
/**
* 背包容量
*/
int c;
/**
* 重量数组
*/
int[] w;
/**
* 价值数组
*/
int[] v;
public static void main(String[] args) {
debug();
test();
}
static void debug() {
// 固定参数用于调试
int[] w = new int[]{1, 3, 5, 7};
int[] v = new int[]{2, 5, 9, 15};
int c = 14;
commonTest(w, v, c);
}
static void test() {
int testTimes = 1000 * 1000;
int num = 10;
int wv = 100;
for (int i = 0; i < testTimes; i++) {
int[] w = new int[num];
int[] v = new int[num];
// 保证非0
int c = (int) (Math.random() * wv) + 1;
for (int j = 0; j < num; j++) {
w[j] = (int) (Math.random() * wv + 1);
v[j] = (int) (Math.random() * wv + 1);
}
commonTest(w, v, c);
}
System.out.println("success");
}
static void commonTest(int[] w, int[] v, int c) {
System.out.println("w = " + Arrays.stream(w).boxed().map(Objects::toString).collect(Collectors.joining(",")));
System.out.println("v = " + Arrays.stream(v).boxed().map(Objects::toString).collect(Collectors.joining(",")));
System.out.println("c = " + c);
KnapsackRecursion knapsackRecursion = new KnapsackRecursion();
knapsackRecursion.setW(w);
knapsackRecursion.setV(v);
knapsackRecursion.setC(c);
// 对数器解法
int result1 = knapsackRecursion.simple();
// 暴力递归解法
int result2 = knapsackRecursion.forceRecursion(w.length, c);
// 动态规划解法
int result3 = knapsackRecursion.dynamicPrograming();
// 动态规划省空间解法
int result4 = knapsackRecursion.dynamicProgramingPro();
System.out.println("result1 = " + result1);
System.out.println("result2 = " + result2);
System.out.println("result3 = " + result3);
System.out.println("result4 = " + result4);
if (result1 != result2 || result1 != result3 || result1 != result4) {
System.err.println("fail");
System.exit(0);
} else {
System.out.println("=====================");
}
}
/**
* 对数器
*/
int simple() {
int max = 0;
// n 个物品的全组合
for (int num = 1; num < 1 << w.length; num++) {
// 选中物品的总重量
int allW = 0;
// 选中物品的总价值
int allV = 0;
// 第num中情况的背包价值;
for (int i = 0; i < w.length; i++) {
if ((1 << i & num) != 0) {
allV += v[i];
allW += w[i];
}
}
// 未超出背包容量的组合选的总重量与当前最大值比较
if (allW <= c) {
max = Math.max(max, allV);
}
}
return max;
}
/**
* 暴力递归版本
* 返回扫过第n个元素后背包的最大价值。<br>
* 第n个元素的结果依赖第n-1个元素的结果,从n开始递归
*
* @param n 元素位置
* @param wight 来到第n个元素时背包的剩余容量
* @return 对当前元素决策后的最大价值
*/
int forceRecursion(int n, int wight) {
if (n <= 0) {
// 递归终止条件
return 0;
} else if (w[n - 1] > wight) {
// 当前物品重量超出背包剩余容量,返回不放该物品的最大价值
return forceRecursion(n - 1, wight);
} else {
// 返回放和不放当前物品所能达到的最大价值的较大值
return Math.max(forceRecursion(n - 1, wight), v[n - 1] + forceRecursion(n - 1, wight - w[n - 1]));
}
}
/**
* 动态规划版本
* 动态规划表示例:
* w v 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
* 1 2 0 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
* 3 5 0 2 2 5 7 7 7 7 7 7 7 7 7 7 7 7 7
* 5 9 0 2 2 5 7 9 11 11 14 16 16 16 16 16 16 16 16
* 7 15 0 2 2 5 7 9 11 15 17 17 20 22 24 26 26 29 31
*/
int dynamicPrograming() {
int n = w.length;
int[][] dp = new int[n][c + 1];
// 初始化第一列,即剩余容量为零,价值为0
for (int i = 0; i < n; i++) {
dp[i][0] = 0;
}
// 初始化动态规划表的第一行
for (int i = 1; i <= c; i++) {
if (w[0] > i) {
dp[0][i] = 0;
} else {
dp[0][i] = v[0];
}
}
for (int i = 1; i < n; i++) {
for (int j = 1; j <= c; j++) {
if (w[i] > j) {
dp[i][j] = dp[i - 1][j];
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i]);
}
}
}
return dp[n - 1][c];
}
/**
* 动态规划省空间版本
* 求解动态规划表第i行时,只依赖第i-1行的记录,所以可以压缩动态规划表为1行。
* 求解单行时,从后往前填充
*/
int dynamicProgramingPro() {
int n = w.length;
int[] dp = new int[c + 1];
dp[0] = 0;
// 初始化
for (int i = 1; i <= c; i++) {
int value = 0;
if (i >= w[0]) {
value = v[0];
}
dp[i] = value;
}
for (int i = 1; i < n; i++) {
for (int j = c; j > 0; j--) {
if (j >= w[i]) {
dp[j] = Math.max(dp[j], v[i] + dp[j - w[i]]);
}
}
}
return dp[c];
}
}