动态规划之经典案例《0-1背包问题》详解

0-1背包问题描述

有一个可装重量为w的背包和n件物品,每件物品都有重量和价值两个属性,且每件物品只有一件,只能选择装入或者不装,能装入的最大物品价值是多少?

一、贪心算法分析

看到求最值问题,一般是使用动态规划来解。为什么不使用贪心算法来解?因为贪心算法求的是局部最优解,而动态规划才能求得全局最优解。

举个例子:

现在有以下物品重量和价值数组W、V,背包最多装入9kg物品。
W = [4,6,5,2]
V = [7,8,5,3]

使用贪心算法时,每一步要做的就是对于第i件物品,我取还是不取,且只能抉择一次。这就需要使用一个标准来衡量后,进行决策。

  • 若使用价值重量比(v/w)作为贪心算法的决策方向,则贪心算法的每一步都选择重量轻价值高的物品。4件物品的价值重量比为:1.75、1.3、1、1.5,这样依次会选择第1件、第4件,总价值为10,重量为6,剩余3kg已不能再装入物品。但若选择第2件和第4件,总重量8,总价值为11,这又是更好地选择。若选择第1件和第3件,总重量9,总价值12,这又是更好的选择。所以,若单纯以价值重量比作为贪心的决策方向,是不能求得全局最优解的。
  • 如果决策标准是价值最高的物品呢?即先选第2件,再选第4件,总重量8,总价值11。那又能找到更好地选择:先选第1件,再选第3件,这样总重量9,总价值12。

所以总而言之,贪心算法只能求得一个差强人意的结果,但不是最好的结果。

二、动态规划分析

案例:

现在有m件物品,重量和价值表示为W、V数组,背包中最多装入n(kg)物品。输入m=3,n=4。
W = [2,1,3]
V = [4,2,3]

如果用动态规划解决背包问题需要怎样考虑呢?它不像贪心算法一样,对于第i件物品,选择取还是不取,且只能抉择一次。而是面对前i件物品,在背包容量为j的情况下,第i件物品我要不要取。动态规划需要枚举只看前i(0 ≤ i ≤ n)件物品,背包容量为j(0 ≤ j ≤ w)时所有的物品排列组合的情况,然后将在当时情况下能获取到的最大的价值记录下来。由此,动态规划数组需要保存什么值就显而易见了,见下。

1.动态规划需要考虑的三要素

  • 动态规划数组的意义:这里要考虑到两个状态:需要在前几件物品(i)中进行抉择和背包容量(j)。所以需要定义一个二维数组dp[i][j],表示为:在只考虑前i件物品的情况下,当背包容量为j时,枚举所有的物品排列组合情况获得的价值,记录最大价值。

  • 动态规划的初始值:当i=0时,表示只在前0件物品中做选择,也就是一件物品都不考虑,这种情况下不管背包容量为多少,能装的最大价值都是0;当j=0时,表示背包容量为0,这种情况下不管考虑前几件物品,都不能将其装入背包,即能装的最大价值也是0。

  • 状态转移方程:这里是最难考虑的。在只考虑前i件物品的情况下,是否取第i件物品怎么决策呢?现在定义第i件物品的重量为w[i],价值为v[i]

    (1)若当前背包的总空间j不能容纳第i件物品,就不能将第i件物品放入背包,此时背包内最大价值就应该等于前i-1件物品的总价值,即dp[i - 1][j],那么就去前面查一下已经计算出的dp[i - 1][j]是多少,即dp[i][j] = dp[i - 1][j]

    (2)若当前背包总空间j能容纳第i件物品,那就需要考虑一个问题:虽然当前背包能装下第i件物品,我一定要把它装进去吗?当然是不一定,如果这样考虑就变成了贪心算法(很容易理解)。因为第i件物品装进去虽然会增大背包的总价值,但同时也减少了背包的可用容量,可以这样理解:之前已经从前i-1件物品中选择了一部分放入了背包,如果此时放入第i件物品,就可能(并不是一定)需要从背包中已经放入的物品中再取出一部分,这样其实是增大背包总价值的同时也减少了总价值。所以需要先衡量第i件物品放进去和不放进去哪个总价值更大。分为以下<1>、<2>情况考虑:

    <1>将其放入背包,此时背包内就已经用掉了w[i]的容量,且最少有v[i]的价值了,那么剩余j - w[i]的容量最多能装多少价值的物品呢?就需要去查查已经计算出的只考虑前i-1件物品时能装的最大价值dp[i - 1][j - w[i]]是多少,这样算完后总价值就表示为dp[i][j] = dp[i - 1][j - w[i]] + v[i]。(第i件物品的重量w[i] = W[j-1],第i件物品的价值v[i] = V[i-1],因为在题设中给的代表物品重量和价值的W、V数组是从0开始索引的,因为现在只是理思路,暂时用小写的w[i]v[i]来标记,等写代码时再替换为大写的W和V。)

    <2>不将其放入背包,此时背包内总价值还是前i-1件物品的总价值,即dp[i][j] = dp[i - 1][j]

最终放还是不放呢?取二者的最大值:dp[i][j] = max{dp[i - 1][j - w[i]] + v[i] , dp[i - 1][j]}

2.伪代码描述

总结以上三点,现在开始枚举所有情况,假设物品件数为m,背包重量为n,用伪代码表示:

int dp[m+1][n+1];//为什么+1,因为枚举时有取前0件和背包重量为0的情况
dp[0][?] = dp[?][0] = 0;//两种初始值情况

for(i = 1; i <= m; ++i){
	for(j = 1; j<= n; ++j){
		if(背包容量为j时装不下第i件物品){
			dp[i][j] = dp[i - 1][j];
		}
		else if(背包容量为j时能装下第i件物品){
			//进行决策,选择装还是不装第i件物品
			dp[i][j] = max{dp[i - 1][j - w[i]] + v[i], dp[i - 1][j]};
		}
	}
}

3.备注

此处有一点需要注意,不理解很容易陷入误区:在考虑第i件物品要不要取时,前面已经选择的物品并不是固定不变的,即假设我在容量为4的时候选择了第一、二、三件物品,在容量为5的时候我可能会放弃第二件而装入第四件物品,反正是怎么更好怎么选。

4.使用C++语言描述

将上面的案例参考为代码转换为代码.注意,上面提到过,小写w[i]v[i]对应的是大写W[i - 1]V[i - 1]

int package01(int m, int n, vector<int>& W, vector<int>& V){
	vector<vector<int>> dp(m+1, vector<int>(n+1, 0));//将二维数组所有值初始化为0
	for(int i = 1; i <= m; ++i){//枚举只考虑前1,2,3...i件物品的情况
		for(int j = 1; j <= n; ++j){//枚举背包容量为1,2,3...j的情况
			if(j < W[i-1]){//背包容量为j时装不下第i件物品
				dp[i][j] = dp[i - 1][j];
			}else{//背包容量为j时能装下第i件物品
				//进行决策,选择装还是不装第i件物品
				dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - W[i-1]] + V[i - 1]);
			}
		}
	}
	return dp[m][n];
}

5.画图表示代码执行过程

  1. 首先,建立二维数组的过程可以理解为画了一张(m+1)*(n+1)的表格,即红色区域,dp[i][j]即为每一个红色格子:在这里插入图片描述
  2. 初始值:
    在这里插入图片描述
  3. 代码执行过程:
    在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 6
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,关于动态规划的0-1背包问题及改进,我可以给您一些解释和建议。 0-1背包问题是一个经典动态规划问题,它的主要思想是将一个可装载重量为W的背包装满所装载物品的价值最大化。具体来说,我们有n个物品,每个物品有一个重量w和一个价值v,我们需要选择一些物品放入背包中,使得它们的总重量不超过W,且总价值最大。 经典的0-1背包问题可以使用动态规划算法来解决。我们可以定义一个二维数组dp[i][j]表示前i个物品放入一个容量为j的背包中所能获得的最大价值。状态转移方程如下: - 当j<w[i]时,dp[i][j] = dp[i-1][j],即当前背包容量不足以放下第i个物品,只能选择不装入背包。 - 当j>=w[i]时,dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i]]+v[i]),即可以选择不装入第i个物品,此时最大价值为dp[i-1][j];或者选择装入第i个物品,此时最大价值为dp[i-1][j-w[i]]+v[i]。 最终的结果为dp[n][W]。 以上是经典的0-1背包问题的解法。但是在实际应用中,我们可能会遇到一些特殊情况,比如物品的价值和重量没有整数限制,此时我们可以使用分数背包问题算法;或者物品的数量很多,此时我们可以使用优化的动态规划算法来解决。 具体来说,我们可以使用一些优化方法来减少计算量,比如: - 倒序遍历:因为状态转移方程中的dp[i-1][j]只依赖于dp[i][j]和dp[i][j-w[i]],所以可以倒序遍历j的值,使得后面的状态可以利用前面已经计算好的状态,减少计算量。 - 滚动数组:由于dp[i][j]只依赖于dp[i-1][.],所以我们可以使用滚动数组的方式,只存储两行数据,从而减少空间复杂度。 - 贪心算法:对于某些特殊情况,可以使用贪心算法来进行优化,比如物品的价值与重量比率相同,此时可以按照单位重量的价值从大到小排序,然后依次装入。 以上是一些常用的优化方法,具体的实现可以根据不同的情况进行选择。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值