0 1背包最大价值问题解析及相关问题| 豆包MarsCode AI 刷题

116 阅读5分钟

问题描述——0,1背包最大价值问题 - MarsCode

一个旅行者外出旅行时需要将 n 件物品装入背包,背包的总容量为 m。每个物品都有一个重量和一个价值。你需要根据这些物品的重量和价值,决定如何选择物品放入背包,使得在不超过总容量的情况下,背包中物品的总价值最大。

给定两个整数数组 weights 和 values,其中 weights[i] 表示第 i 个物品的重量,values[i] 表示第 i 个物品的价值。你需要输出在满足背包总容量为 m 的情况下,背包中物品的最大总价值。

测试样例

样例1:

输入:n = 3 ,weights = [2, 1, 3] ,values = [4, 2, 3] ,m = 3
输出:6

样例2:

输入:n = 4 ,weights = [1, 2, 3, 4] ,values = [2, 4, 4, 5] ,m = 5
输出:8

c++核心代码

int solution(int n, int m,vector<int> weights, vector<int> values) {
    vector<vector<int>> dp(n + 1, vector<int>(m + 1, 0));//初始化 把所有的置为0
    for(int i=1;i<=n;i++){//不从0开始,是因为,第0行 没有物品,那么总价值 就是0
        for(int j=0;j<=m;j++){//这个的意思 j 代表 体积  每一行 0-m 
            //这个就是 当 第i个 物品 体积 为 j 的时候 ,不选 第i个 物品 的情况 
            dp[i][j]=dp[i-1][j];
     
            //这个就选 第i个 物品 的情况,曲线一下,先减去i的体积 用来找最优
            if(j>=weights[i-1]) dp[i][j]=std::max(dp[i][j],dp[i-1][j-weights[i-1]]+values[i-1]);
        }
    }
    return dp[n][m];
}

思路分析

首先,题意大概是要求我们尽量多装,且在不超过其最大容量m的前提下,总价值最大

对于0 1背包的问题,一件物品——要么装,要么不装,是一个整体,不可切割,且只能选一次

同时,我们不一定需要正好装入m体积的物品,只需要小于等于m

这种问题一般都会用dp的思想来求解。

求解过程

  • 动态规划dp是什么

    从数学角度出发,动态规划(Dynamic Programming,DP)是运筹学的一个分支,是求解决策过程最优化的过程。
    但在算法中,dp与数学定义还是有一定区别。简单来说,指的是将一个复杂的问题,分解成简单的问题(用一种递归的方式)
    本质:分治(与递归没有本质区别)+ 最优解 ,很多就是一些细节的不同。

动态规划 和 递归或者分治 没有根本上的区别(关键看有无最优的子结构)
共性:找到重复子问题
差异性:最优子结构、中途可以淘汰次优解

  • 闫氏dp分析法
    • 状态表示F(i,j)
      • 集合
      • 属性 :MAX MIN COUNT
    • 状态计算
      • 将集合划分为不同的子集
      • 划分依据 : 当前的 i 物品 是选还是不选
      • 子集满足的条件 : 不重复(最值可不考虑)、不遗漏。 1.png

回到题目上,用上述的方法进行分析:

1.状态表示

  • 集合f[i][j]:所有只从当前i个物品,并且总体积≤j中来选
  • 属性:MAX

2.状态计算

  • 当前背包容量不够(j < v[i]),没得选,因此前 i 个物品最优解即为前 i−1个物品最优解:f[i][j] = f[i - 1][j]。
  • 如果可以选 :f[i][j] = f[i - 1][j - v[i]] + w[i]

1.png

因此,当输入:n = 4 ,weights = [1, 2, 3, 4] ,values = [2, 4, 4, 5] ,m = 5

分析问题可得,每个物品都可以选或者不选,且只能选一次,
对于不选的元素的属性 f[i][j] = f[i-1][j]
对于选的元素的属性f[i][j]采用曲线救国的方法 f[i][j] = f[i - 1][j - v[i]] + w[i]
即元素的属性是由另一个元素的属性转移过来的
可得未优化的状态方程: f[i][j] = max(f[i - 1][j],f[i - 1][j - v[i]] + w[i])

230262_e743e7a057-01背包.jpg

即可得最大价值为8,与预期结果一致,推导过程合理。

由此我们便也很形象的表示出来,我们注意到,每次i层的数据都是根据i-1层的数据进行更新,也就是说更新到i层时,i - 2 i - k层我们便不再需要,所以每次只需要维护两层就可以,进一步观察可知,如果用一个一维数组存放i - 1 层数据,每次更新 i 层时i - 1层的数据都会留在数组中,而我们如果从大到小进行更新的话,正好可以在i - 1层数据被覆盖前用它,所以就可以优化成一维数组

优化后核心区代码

//优化
for(int i = 1;i <= n;++i)
    for(int j = m;j >= v[i];--j)
        f[j] = max(f[j],f[j-v[i]]+w[i]);
        

为什么优化之后是 j >= v[i]?
答:一方面避免数组越界,另一方面,就是,i 行的物品,只与 i-1 行有关,但当优化为 i-1 层的数据会被 第i行的数据所覆盖,而如果逆序遍历,就可以在后边比较时,比较的是 i-1行的数据。

后续会将完全、多重、分组背包的思路和个人思考及进行更新,感谢大雪菜哈哈~