• 动态规划:09 0-1背包理论基础I


    动态规划:09 0-1背包理论基础I

    背包问题概述

    对于面试的话,其实掌握01背包,和完全背包,就够用了,最多可以再来一个多重背包。

    如果这几种背包,分不清,就看下图

    在这里插入图片描述

    leetcode上连多重背包的题目都没有,这就告诉我们,01背包和完全背包就够用了。

    而完全背包又是01背包稍作变化而来,即:完全背包的物品数量是无限的。

    所以背包问题的理论基础重中之重是01背包,一定要理解透!

    leetcode上没有纯01背包的问题,都是01背包应用方面的题目,也就是需要转化为01背包问题

    所以后面先通过纯01背包问题,把01背包原理理解清楚,后续再写eetcode题目的时候,重点就是讲解如何转化为01背包问题了

    01背包

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

    这是标准的背包问题,以至于很多同学看了这个自然就会想到背包,甚至都不知道暴力的解法应该怎么解了。

    这样其实是没有从底向上去思考,而是习惯性想到了背包,那么暴力的解法应该是怎么样的呢?

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

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

    在下面的讲解中,我举一个例子:

    背包最大重量为4。

    物品为:

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

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

    以下讲解和图示中出现的数字都是以这个例子为例。

    二维dp数组01背包 五部曲

    1. 确定dp数组含义:编号为0-i的物品,任取放入容量为j的背包,最大价值为dp[i][j]

      对于背包问题,有一种写法, 是使用二维数组,即dp[i][j] 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少

      在这里插入图片描述

      要时刻记着这个dp数组的含义,下面的一些步骤都围绕这dp数组的含义进行的,如果哪里看懵了,就来回顾一下i代表什么,j又代表什么。

    2. 确定递归公式:dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[j])

      再回顾一下dp[i][j]的含义:从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少。那么可以有两个方向推出来dp[i][j],

      不放物品i,最大价值为:dp[i - 1][j]

      放物品i:dp[i - 1][j - weight[i]] + value[j]

    3. dp数组初始化:dp[0][j] dp[i][0]

      关于初始化,一定要和dp数组的定义吻合,否则到递推公式的时候就会越来越乱

      首先从dp[i][j]的定义出发,如果背包容量j为0的话,即dp[i][0],无论是选取哪些物品,背包价值总和一定为0。

      在看其他情况。

      状态转移方程 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的物品的时候,各个容量的背包所能存放的最大价值。

      那么很明显当 j < weight[0]的时候,dp[0][j] 应该是 0,因为背包容量比编号0的物品重量还小。

      当j >= weight[0]时,dp[0][j] 应该是value[0],因为背包容量放足够放编号0物品。

      物品i\容量j01234
      00value[0](如果能放的话)value[0](如果能放的话)value[0](如果能放的话)value[0](如果能放的话)
      10max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[j])
      20
      30
      40

      dp[0][j] 和 dp[i][0] 都已经初始化了,那么其他下标应该初始化多少呢?

      其实从递归公式: dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]); 可以看出dp[i][j] 是由左上方数值推导出来了,那么 其他下标初始为什么数值都可以,因为都会被覆盖。

      初始-1,初始-2,初始100,都可以!

      但只不过一开始就统一把dp数组统一初始为0,更方便一些。

    4. 遍历顺序:二维数组的话,先遍历背包或先遍历物品都可以

      dp[i][j] = max(dp[i - 1]\j], dp[i - 1][j - weight[i]] + value[i]); 递归公式中可以看出dp[i][j]是靠dp[i-1][j]和dp[i - 1][j - weight[i]]推导出来的。

      dp[i-1][j]和dp[i - 1][j - weight[i]] 都在dp[i][j]的左上角方向(包括正上方向)

    5. debug:打印dp数组

    题目与代码

    题目

    输入

    第一行包含两个正整数,第一个整数 M 代表研究材料的种类,第二个正整数 N,代表小明的行李空间。

    第二行包含 M 个正整数,代表每种研究材料的所占空间。

    第三行包含 M 个正整数,代表每种研究材料的价值。

    输出

    输出一个整数,代表小明能够携带的研究材料的最大价值。

    样例输入
    6 1
    2 2 3 1 5 2
    2 3 1 5 4 3
    
    • 1
    • 2
    • 3
    样例输出
    5
    
    • 1

    代码

    import java.util.Arrays;
    import java.util.Scanner;
    
    public class Main{
        public static void main(String... args) {
            Scanner scanner = new Scanner(System.in);
            int M;
            int bagSize;
            M = scanner.nextInt();
            bagSize = scanner.nextInt();
    
    //        System.out.println("M:" + M +" bagSize:" + bagSize);
    
            int[] weight = new int[M];
            int[] value = new int[M];
            for(int i = 0; i < M; i++) {
                weight[i] = scanner.nextInt();
            }
    //        System.out.println("weight数组:" + Arrays.toString(weight));
    
            for(int i = 0; i < M; i++) {
                value[i] = scanner.nextInt();
            }
    //        System.out.println("value数组:" + Arrays.toString(value));
    
            int res = testWeightBagProblem(weight, value, bagSize);
            System.out.println(res);
        }
    
        public static int testWeightBagProblem(int[] weight, int[] value, int bagSize) {
            int[][] dp = new int[weight.length][bagSize + 1];
            //初始化dp数组,第一列默认值就是0
            for(int j = weight[0]; j <= bagSize; j++) {
                dp[0][j] = value[0];
            }
    
            for(int i = 1; i < weight.length; i++) {
                for(int j = 1; j <= bagSize; j++) {
                    if(j < weight[i]) {
                        dp[i][j] = dp[i - 1][j];
                    } else {
                        dp[i][j] = Math.max(dp[i - 1][j], (dp[i - 1][j - weight[i]] + value[i]));
                    }
                }
            }
    
    //        testAndVerify(dp);
    
            return dp[weight.length - 1][bagSize];
        }
    
        public static void testAndVerify(int[][] dp){
            //验证dp数组
            System.out.println(Arrays.deepToString(dp));
        }
    }
    
    • 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
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    总结

    这里其实可以发现最简单的是推导公式了,推导公式估计看一遍就记下来了,但难就难在如何初始化和遍历顺序上

  • 相关阅读:
    java蚁群算法求解旅商问题
    C++ Reference: Standard C++ Library reference: C Library: cctype: isspace
    C++异步:libunifex中的concepts详解!
    【Truffle】一、Truffle的安装与部署
    2023年天津中德应用技术大学专升本物流管理专业考试大纲
    git常用命令以及常见错误处理
    JavaIO流:概述
    网页翻译软件-网页自动采集翻译软件免费
    测试左移:传统测试方式该如何过渡
    DL之GRU:基于2022年6月最新上证指数数据集利用GRU算法预测最新股票上证指数实现回归预测
  • 原文地址:https://blog.csdn.net/m0_64037602/article/details/133860633