问题描述——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 物品 是选还是不选
- 子集满足的条件 : 不重复(最值可不考虑)、不遗漏。
- 状态表示F(i,j)
回到题目上,用上述的方法进行分析:
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]。
因此,当输入: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])
即可得最大价值为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行的数据。
后续会将完全、多重、分组背包的思路和个人思考及进行更新,感谢大雪菜哈哈~