0-1背包问题

image.png
我们的题目是
image.png
背包的填表过程
image.png

  1. package com.atguigu.dynamic;
  2. public class KnapsackProblem {
  3. public static void main(String[] args) {
  4. // TODO Auto-generated method stub
  5. int[] w = {1, 4, 3};//物品的重量
  6. int[] val = {1500, 3000, 2000}; //物品的价值 这里val[i] 就是前面讲的v[i]
  7. int m = 4; //背包的容量
  8. int n = val.length; //物品的个数
  9. //创建二维数组,
  10. //v[i][j] 表示在前i个物品中能够装入容量为j的背包中的最大价值
  11. int[][] v = new int[n+1][m+1];
  12. //为了记录放入商品的情况,我们定一个二维数组
  13. int[][] path = new int[n+1][m+1];
  14. //初始化第一行和第一列, 这里在本程序中,可以不去处理,因为默认就是0
  15. for(int i = 0; i < v.length; i++) {
  16. v[i][0] = 0; //将第一列设置为0
  17. }
  18. for(int i=0; i < v[0].length; i++) {
  19. v[0][i] = 0; //将第一行设置0
  20. }
  21. //根据前面得到公式来动态规划处理
  22. for(int i = 1; i < v.length; i++) { //不处理第一行 i是从1开始的
  23. for(int j=1; j < v[0].length; j++) {//不处理第一列, j是从1开始的
  24. //公式
  25. if(w[i-1]> j) { // 因为我们程序i 是从1开始的,因此原来公式中的 w[i] 修改成 w[i-1]
  26. v[i][j]=v[i-1][j];
  27. } else {
  28. //说明:
  29. //因为我们的i 从1开始的, 因此公式需要调整成
  30. //v[i][j]=Math.max(v[i-1][j], val[i-1]+v[i-1][j-w[i-1]]);
  31. //v[i][j] = Math.max(v[i - 1][j], val[i - 1] + v[i - 1][j - w[i - 1]]);
  32. //为了记录商品存放到背包的情况,我们不能直接的使用上面的公式,需要使用if-else来体现公式
  33. if(v[i - 1][j] < val[i - 1] + v[i - 1][j - w[i - 1]]) {
  34. v[i][j] = val[i - 1] + v[i - 1][j - w[i - 1]]; //之所以对此处进行标记,是因为只有这个地方会出现多个商品的情况
  35. //把当前的情况记录到path
  36. path[i][j] = 1;
  37. } else {
  38. v[i][j] = v[i - 1][j];
  39. }
  40. }
  41. }
  42. }
  43. //输出一下v 看看目前的情况
  44. for(int i =0; i < v.length;i++) {
  45. for(int j = 0; j < v[i].length;j++) {
  46. System.out.print(v[i][j] + " ");
  47. }
  48. System.out.println();
  49. }
  50. System.out.println("============================");
  51. //从后往前来处理,是因为动态规划是不断优化以至结果最优,那么必然要从后往前看
  52. int i = path.length - 1; //行的最大下标
  53. int j = path[0].length - 1; //列的最大下标
  54. while(i > 0 && j > 0 ) { //从path的最后开始找
  55. if(path[i][j] == 1) { //因为只允许一个商品,所以判断完一行,就该下一行了
  56. System.out.printf("第%d个商品放入到背包\n", i);
  57. j -= w[i-1]; //w[i-1],i代表商品的重量,j代表能容纳的重量,所以确定剩余的可容纳重量,就直接确定行数了
  58. }
  59. i--; //确定完一个商品后,进入下一个商品的确定
  60. }
  61. }
  62. }

子集背包问题

image.png
要求划分为两个和相等的子集,实际上可以表达为
给一个可装载重量为 sum / 2 的背包和 N 个物品,每个物品的重量为 nums[i]。现在让你装物品,是否存在一种装法,能够恰好将背包装满
这样就转换为背包问题了
image.png
image.png

  1. package ActualCombat;
  2. public class two {
  3. public static void main(String[] args) {
  4. int w=0;
  5. int s=0;
  6. int[] sumweight = {1,5,11,5};
  7. int[] sumweight1 = new int [sumweight.length]; //存储数组1
  8. int[] sumweight2 = new int [sumweight.length]; //存储数组2
  9. int sum = 0;
  10. for (int i = 0; i < sumweight.length; i++) { //计算总和,判断是否可分割
  11. sum += sumweight[i];
  12. }
  13. int key2;
  14. int key1 = key2 = sum/2;
  15. if (sum%2 != 0) {
  16. System.out.println("false"); //余数不为0,表示不可分割
  17. }else {
  18. for (int i = 0; i < sumweight.length; i++) { //逐渐递减,相差大于0,表示可以存储进数组1,反之用数组2存储
  19. if ((key1 - sumweight[i]) >= 0) {
  20. key1 = key1 - sumweight[i];
  21. sumweight1[w] = sumweight[i];
  22. w++; //记录数组1存储的个数
  23. }else {
  24. key2 = key2 - sumweight[i];
  25. sumweight2[s] = sumweight[i];
  26. s++; //记录数组2存储的个数
  27. }
  28. }
  29. for (int i = 0; i < w; i++) {
  30. System.out.print(sumweight1[i]+"\t");
  31. }
  32. System.out.println("\n");
  33. for (int i = 0; i < s; i++) {
  34. System.out.print(sumweight2[i]+"\t");
  35. }
  36. }
  37. }
  38. }

示例1运行结果
image.png
示例2运行结果
image.png
本小节代码为我亲自所写,不像前一小节是老师的代码,而且只是在理解基础上,希望将来再次写0-1背包问题,可以自己写出来。当我每次强调是自己写出的时候,主要是想说也许这不是最简洁最快的方法,因为我基础不怎样,所以还是以解决问题为主。希望后面的进步越来越大
说句来说话,当可以分割的时候,后面的思路更像是我前面做的零钱兑换,接下来的这个完全背包其实也就是零钱兑换问题

完全背包问题

题目实际上就是把0-1背包问题的只允许拿一件物品改为可以无限拿同一个物品
我就不写这个代码了,你自己去看零钱兑换那一小节
零钱兑换