0-1背包问题

本文详细解释了0-1背包问题的逻辑,使用动态规划求解dp数组,包括二维到一维的转换,以及如何通过先遍历物品再倒序遍历容量来优化空间复杂度。重点讲解了dp数组的定义、初始化和递推公式。
摘要由CSDN通过智能技术生成

0-1背包问题

描述

0-1背包问题的基本逻辑就是,在n个重量不同,价值不同的物品中任选几个,使得可容纳重量位w的背包达到最大价值。

举例

有以下三个物品

物品重量价值
物品01020
物品1410
物品2615

背包的容量为10

明确dp数组的定义

dp[i][j] 表示容量为j的背包,在物品i之前任取物品能达到的最大价值
对于我们这个问题就是要确定dp[2][10]的值,就是代表了容量为10的背包,在物品2之前任取物品所能找到的最大价值

初始化
i/j012345678910
物品0000000000020
物品10
物品20

如上表所示,第一列,也就是dp[i][0]背包为0的前提下,肯定装不了物品,所以初始化为0
第一行,dp[0][j]不同的背包容量下装物品0,那显然只有装得下物品0的时候价值为物品0的价值,否则,价值就是0

dp公式

对于每一个dp[i][j]有两种情况

  1. 选择装物品i能达到的最大价值
    选择物品i换句话说就是一定要有物品i,也就是说,在选择物品i的前提下,在选择剩下的物品,看能达到的最大价值,那么递推公式就是
    dp[i][j] = dp[i-1][j-weight[i]] + value[i]
  2. 不选择物品i能达到的最大价值
    不选择,很容易写出如下递推公式
    dp[i][j] = dp[i-1][j]
    最终dp公式如下表示
    dp[i][j] = max(dp[i-1][j-weight[i]] + value[i], dp[i-1][j])
遍历顺序

如何便利dp数组呢?

i/j012345678910
物品0000000000020
物品10
物品20

根据递推公式可以看出,每一个dp[i][j]都是和他的前一行的数据有关,并且是对应位置前一行的前面的列有关,因为我们已经做了如上的初始化,所以先遍历物品和遍历容量都可以。

伪代码
dp[n][m+1] // n个物品,背包容量为m
weight[n] // n个物品的重量
value[n]  // n个物品的价值

// 初始化
for(i : 0 -> n-1) {
	dp[i][0] = 0 // 第一列初始化为0
}
for(j : 0 -> m) {
	dp[0][j] = j >= weight[0] ? value[0] : 0 // 第一行初始化
}

// 遍历
for(i : 1 -> n-1) {
	for(j : 1 -> m) {
		 dp[i][j] = max(j > weight[i] ? dp[i-1][j-weight[i]] + value[i] : 0
		 		         , dp[i-1][j])
	}
}

// 返回结果
return dp[n-1][m]

从上述过程中可以看到,对于dp数组,我们在遍历当前行的元素时,所需要的是上一行并且是当前列的前面列的元素,所以,这里为了节省空间,可以将二位dp压缩成一维数组,节省空间复杂度

一维dp定义

dp[j] 代表容量为j的背包所能装在的最大价值

dp数组的递推公式

跟二维dp对应的一个结果,应为将二维数组压缩,上一行的元素保存在当前行,所以不用i-1

  1. 选择装物品i能达到的最大价值
    dp[j] = dp[j-weight[i]] + value[i]
  2. 不选择物品i能达到的最大价值
    dp[j] = dp[j]
    最终dp数组的递推公式如下所示
    dp[j] = max(dp[j-weight[i]] + value[i], dp[j])
dp数组初始化

根据,dp数组的递推公式可以看出,将dp数组全部初始化为0即可

遍历顺序

对于二维dp来说,先遍历物品或者是先遍历重量都可以,但是对于一位dp来说一定要先便利物品在遍历重量,并且,在遍历重量的时候,还需要倒序遍历。原因如下

i/j012345678910
物品0000000000020
物品10
物品20
  1. 倒序遍历重量的原因就是,因为你需要的是前一行并且当前列前一列的元素,因为此时历史元素是存在当前行的,如果你是从前往后遍历,就会破坏之前的列
  2. 先遍历物品的原因就是,因为倒序遍历重量是一个前提,在这个前提下,你只能先遍历物品,否则先遍历重量的话,就没有所需要的历史元素的记录
伪代码
dp[m+1] // n个物品,背包容量为m
weight[n] // n个物品的重量
value[n]  // n个物品的价值

// 初始化
for(i : 0 -> m) {
	dp[i] = 0 // 全部初始化为0
}
// 遍历
for(i : 0 -> n-1) { // 先遍历物品
	for(j : m -> 0) { // 倒序遍历背包容量
		 if(j <= weight[i])
		 	break
		 dp[j] = max(dp[j-weight[i]] + value[i], dp[j])
	}
}

// 返回结果
return dp[m]
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值