• 解析:动态规划 01背包


    动态规划:01背包

    在这里插入图片描述


    N物品和一个最多能背重量W 的背包。第i件物品的重量是 weight[i],得到的价值是 value[i] 。每件物品只能用一次,求解将哪些物品装入背包里 物品价值总和 最大。

    在这里插入图片描述

    每一件物品其实只有两个状态,取或者不取,所以可以使用回溯法搜索出所有的情况,那么时间复杂度就是O(2^n),这里的n表示物品数量。

    所以暴力的解法是指数级别的时间复杂度。进而才需要动态规划的解法来进行优化!


    背包最大重量为4。

    重量价值
    物品0115
    物品1320
    物品2430

    问背包能背的物品最大价值是多少?


    (1)dp[i][j] 表示从下标为 i物品里任意取,放进容量j 的背包,价值总和最大是多少。

    (2)有两个方向可以推出来 dp[i][j]

    • 不放物品 i 的最大价值】由 dp[i - 1][j] 推出,即背包容量j,里面不放物品 i 的最大价值,此时 dp[i][j]就是dp[i - 1][j]
    • 放物品 i 的最大价值】由 dp[i - 1][j - weight[i]] 推出,dp[i - 1][j - weight[i]] 为背包容量为 j - weight[i] 的时候不放物品 i 的最大价值,那么 dp[i - 1][j - weight[i]] + value[i] (物品i的价值),就是背包放物品i得到的最大价值。
      (就是说想象一下,你往一个背包塞一个柚子,你是不是得 “空出这么大空间”,才塞的进去。那么在塞柚子之前,有这么大空间了,它的价值是不是 dp[i - 1][j - weight[i]] ,是的!)

    (3)首先从 dp[i][j] 的定义触发,如果背包容量j为0的话,即dp[i][0],无论是选取哪些物品,背包价值总和一定为0。
    在这里插入图片描述
    (4)状态转移方程 dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]); 可以看出 i 是由 i-1 推导出来,那么i为0的时候就一定要初始化。

    dp[0][j],即:i为0,存放编号0的物品的时候,各个容量的背包所能存放的最大价值。

    // 倒叙遍历
    for (int j = bagWeight; j >= weight[0]; j--) {
        dp[0][j] = dp[0][j - weight[0]] + value[0]; // 初始化i为0时候的情况
    }
    
    • 1
    • 2
    • 3
    • 4

    这个初始化为什么是倒叙的遍历的?正序遍历就不行么?---- 不行!

    正序遍历了,那么物品0就会被重复加入多次!例如代码如下:

    // 正序遍历
    for (int j = weight[0]; j <= bagWeight; j++) {
        dp[0][j] = dp[0][j - weight[0]] + value[0];
    }
    
    • 1
    • 2
    • 3
    • 4

    例如 dp[0][1] 是15,到了dp[0][2] = dp[0][2 - 1] + 15; 也就是dp[0][2] = 30 了,那么就是物品0 被重复放入了

    因为 dp[i-1][j-weight[i]] + value[i] 从左上角取值!正序就会重复放入,倒叙就不会,因为前面没值!

    在这里插入图片描述
    (5)初始化代码

    // 初始化
    int[][] dp = new int[weight.length + 1][bagWeight + 1];
    
    for (int j = bagWeight; j >= weight[0]; j--) {
    	dp[0][j] = dp[0][j - weight[0]] + value[0];
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    (6)遍历过程

    先遍历 物品还是先遍历背包重量呢? 其实都可以!!但是先遍历物品更好理解。

    for(int i = 1; i < weight.length; i++) { // 遍历物品:跳开初始化的第一个物品
        for(int j = 0; j <= bagWeight; j++) { // 遍历背包容量 
            if (j < weight[i]) {
    			dp[i][j] = dp[i - 1][j];  // 背包容量小于当前物品大小,自然放不进去了
    		}                             // 这个是为了展现dp数组里元素的变化
            else {
    			dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
    		}
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在这里插入图片描述
    优化:

    for(int i = 1; i < weight.length; i++) { // 遍历物品:跳开初始化的第一个物品
        for(int j = 0; j <= bagWeight; j++) { // 遍历背包容量 
            if (j >= weight[i]) {
    			dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
    		}
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    再优化:

                           // 遍历物品:遍历的是下标,会使用到 weight[i]、value[i]
    for(int i = 1; i < weight.length; i++) { // 遍历物品:跳开初始化的第一个物品
        for(int j = weight[i]; j <= bagWeight; j++) { // 遍历背包容量 
            dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    (7)全部代码
    在这里插入图片描述

    package org.example;
    
    public class test {
        public static void test_2_wei_bag_problem1() {
            int[] weight = new int[]{1, 3, 4};
            int[] value = new int[]{15, 20, 30};
            int bagWeight = 4;
    
            int[][] dp = new int[weight.length + 1][bagWeight + 1];
    
            for (int j = bagWeight; j >= weight[0]; j--) {
                dp[0][j] = dp[0][j - weight[0]] + value[0];
            }
            
    		// 后面讲到滑动数组还可以优化
            for (int i = 1; i < weight.length; i++) { // 遍历物品:从第二个物品开始遍历,代表你明白初始化边界条件
                for (int j = weight[i]; j <= bagWeight; j++) { // 遍历背包容量
    				dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
                }
            }
    
            // 打印
            for (int i = 0; i < weight.length; i++) {
                for (int j = 0; j <= bagWeight; j++) {
                    System.out.println(dp[i][j]);
                }
                System.out.println();
            }
        }
    
        public static void main(String[] args) {
            test_2_wei_bag_problem1();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    滑动数组:

    package org.example;
    
    public class test {
        public static void test_2_wei_bag_problem() {
            int[] weight = new int[]{1, 3, 4};
            int[] value = new int[]{15, 20, 30};
            int bagWeight = 4;
    
            int[] dp = new int[bagWeight + 1];
    
     		// 把初始化涵盖进去了,但是没有二维数组好理解了,需要深厚的基础才能对滑动数组游刃有余,
     		// 不然考试的时候又手忙脚乱,基础不牢!
     		// 滑动数组背包遍历必须倒叙,因为滑动数组覆盖掉了之前值,而二维数组不会!
     		
     		                // 遍历物品:遍历的是下标,会使用到 weight[i]、value[i]
            for (int i = 0; i < weight.length; i++) { // 遍历物品
                for (int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量
                    dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]); 
                }
            }
            // 放进去的是价值 value[i],dp[j] 就是价值!
            System.out.println(dp[bagWeight]);
        }
    
        public static void main(String[] args) {
            test_2_wei_bag_problem();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    理解成滑动数组:

    1)物品 i = 0 的时候,相当于初始化
    2)物品 i != 0 的时候,现在是滑动数组,只有一个数组,相当于重复第一次的初始化!
    所以要深刻理解初始化!
    ——
    在这里插入图片描述
    ——
    在这里插入图片描述


    动态规划:完全背包

    在这里插入图片描述

  • 相关阅读:
    如何使用内网穿透实现远程公网访问windows node.js的服务端
    计算机视觉全系列实战教程:(八)图像变换-点运算、灰度变换、直方图变换
    安装node-sass出现报错记录
    TensorFlow搭建双向LSTM实现时间序列预测(负荷预测)
    无桥图腾柱PFC电路讲解
    Oracle数据库安装及配置
    多线程JUC 篇 1.1juc的基本知识
    web自动化测试(java+seleium)环境安装
    测不准原理
    Django select_related()方法
  • 原文地址:https://blog.csdn.net/LIZHUOLONG1/article/details/127973291