解决0-1背包问题(方案一):二维dp数组

>>确定dp数组以及下标的含义

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

(1)不放物品i

例如:当背包的容量是1kg,此时物品1和物品2都是无法放进去的 。而物品0是可以放进背包的

那么此种情况下:

物品0,产生的最大的价值为15

物品1 ,产生的最大的价值和前面相同,也就是15

物品2 ,产生的最大的价值和前面相同,也就是15

观察二维表格,以及分析,我们可以这样表示:dp[i-1][j]

思考:**dp[i-1][j]**表示什么意思呢?

看表格的j=0 ,这一列,正是上文所述的这种情况。也就是背包的容量为 j 的时候,可以放入物品i 或者不能放入物品i 的所能产生的最大的价值!

思考:不能放入物品i时,如何计算其产生的最大价值呢?

只要和前面的值相同即可。相当于 copy 下来。

例如物品1(3kg)放不进容量小于3kg的背包。也就是背包容量为1kg,2kg时,无法放入物品1,那么此时这种情况如何计算最大价值呢?很简单,就是和前面的值相同即可。也就是和物品0产生的最大价值相同。

同理,物品2(4kg)放不进容量小于4kg的背包。也就是背包容量为1kg,2kg,3kg时,无法放入物品2,那么此时和物品1产生的最大价值相同即可。

思考:若能放入物品i呢?

分成两种方案:

【方案一】:放入物品i 能产生的最大价值

【方案二】:不放入物品i能产生的最大价值

选取最大价值即可~

例如:当背包的容量是4kg,可以只放入物品2(4kg),但是产生的价值未必是最大的。此时我们思考一下如果不放入物品2,而是放入其他的物品,计算其重量之和为4kg所能产生的价值。

若不放入物品2,可以有如下思路:

① 只放入物品0(1kg),产生价值15

② 只放入物品1(3kg),产生价值20

③ 先放入物品1(3kg),剩余的1kg的容量,用来放入物品0(1kg),总该产生价值35

那么我们如何实现③这种方案呢?

显然,4kg的容量是可以放入物品1(3kg)并可知其产生的价值为20,那么此时可以计算出剩余容量(1kg),接着在表格中找出1kg能产生的最大价值为15,然后求其价值总和为35。

观察二维表格,以及分析,我们可以这样表示:

dp[i-1][j-weight[i]] + value[i]

(2)放入物品i

例如:当背包的容量是4kg,只放入物品2(4kg),产生价值为30

我们可以发现,应该取 max(不放物品i的最大价值,所剩容量能产生的最大价值 + 放物品i的价值)

上文提及不放物品2,而是放入其他物品时,产生的最大价值为35(也就是放入物品0和物品1所产生的最大价值)。35>30。此时我们便可更新背包的容量是4kg时所能 产生的最大价值为35。

最大价值:dp[i][j] = max(dp[i-1][j],dp[i-1][j-weight[i]] + value[i])

(3)动规五部曲:

1.确定dp数组以及下标的含义

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

2.确定递推公式

由dp[i-1][j]推出,即背包容量为j,里面不放物品i的最大价值,此时dp[i][j]就是dp[i-1][j]

由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][j] = max(dp[i-1][j],dp[i-1][j-weight[i]] + value[i]);

3.dp数组如何初始化

【注意】关于初始化,一定要和dp数组的定义吻合,否则到递推公式的时候会乱掉。

从定义出发,若背包容量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物品。

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,更方便一些。

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

4.确定遍历顺序

有两个遍历的维度:物品与背包重量

思考🤔:请问是先遍历物品还是先遍历背包重量呢?

其实都可以!!!但是先遍历物品更加容易理解!!!

cpp 复制代码
// weight数组的大小,就是物品的个数
for(int i=1;i<weight.size();i++) { // 遍历物品
    for(int j=0;j<=bagWeight;j++)  {
        if(j < weight[i]) dp[i][j] = dp[i-1][j];
        else dp[i][j] = max(dp[i-1][j],dp[i-1][j-weight[i]] + value[i]);
    }
}

5.举例推导dp数组

完整代码:

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

void test_1_wei_bag_problem() {
	vector<int> weight = { 1,3,4 };
	vector<int> value = { 15,20,30 };
	int bagWeight = 4;

	// 初始化
	vector<int> dp(bagWeight + 1, 0);
	print(dp,bagWeight);
	for (int i = 0; i < weight.size(); i++) { // 遍历物品
		for (int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量
			dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
		}
	}
	cout << dp[bagWeight] << endl;
}

int main() {
	test_1_wei_bag_problem();
}

思考

cpp 复制代码
// 遍历过程
for(int i=1;i<weight.size();i++) { // 遍历物品
    for(int j=0;j<=bagWeight;j++) {
        if(j-weight[i] >= 0) {
            dp[i][j] = max(dp[i-1][j],dp[i-1][j-weight[i]] + value[i]);
        }
    }
}

其实就是省去了背包容量无法放下物品i的情况时候,和前面值一样的"copy"
相关推荐
R1nG86325 分钟前
CANN资源泄漏检测工具源码深度解读 实战设备内存泄漏排查
数据库·算法·cann
_OP_CHEN34 分钟前
【算法基础篇】(五十六)容斥原理指南:从集合计数到算法实战,解决组合数学的 “重叠难题”!
算法·蓝桥杯·c/c++·组合数学·容斥原理·算法竞赛·acm/icpc
TracyCoder1231 小时前
LeetCode Hot100(27/100)——94. 二叉树的中序遍历
算法·leetcode
九.九1 小时前
CANN HCOMM 底层机制深度解析:集合通信算法实现、RoCE 网络协议栈优化与多级同步原语
网络·网络协议·算法
C++ 老炮儿的技术栈1 小时前
Qt Creator中不写代如何设置 QLabel的颜色
c语言·开发语言·c++·qt·算法
子春一1 小时前
Flutter for OpenHarmony:构建一个 Flutter 数字消消乐游戏,深入解析网格状态管理、合并算法与重力系统
算法·flutter·游戏
草履虫建模7 小时前
力扣算法 1768. 交替合并字符串
java·开发语言·算法·leetcode·职场和发展·idea·基础
naruto_lnq10 小时前
分布式系统安全通信
开发语言·c++·算法
Jasmine_llq10 小时前
《P3157 [CQOI2011] 动态逆序对》
算法·cdq 分治·动态问题静态化+双向偏序统计·树状数组(高效统计元素大小关系·排序算法(预处理偏序和时间戳)·前缀和(合并单个贡献为总逆序对·动态问题静态化
爱吃rabbit的mq10 小时前
第09章:随机森林:集成学习的威力
算法·随机森林·集成学习