背包问题九讲~~01背包

版权声明:Andy https://blog.csdn.net/Alibaba_lhl/article/details/81503453

Description

已知:有一个容量为W的背包和n件物品,第i件物品的重量是w[i],价值是v[i]。

问题:在不超过背包容量的情况下,最多能获得多少价值或收益?

相似问题:在恰好装满背包的情况下,最多能获得多少价值或收益?

限制:每种物品只有一件,可以选择放或者不放(由于对于每件物品,只存在取与不取两种情况,所以该问题被称为 01背包

这里,我们先讨论在不超过背包容量的情况下,最多能获得多少价值或收益。

Think

问题特点:每件物品只有一件,对于每件物品可以选择拿或不拿。

子问题定义状态:

dp[i][j] : 对前i件物品进行选取放到一个容量为j的背包中可以获得的最大价值

状态转移方程:

dp[i][j] = max(dp[i - 1][j],dp[i - 1][j - w[i]] + v[i])

Analysis:考虑我们的子问题,将前i件物品放到容量为W的背包中,若我们只考虑第i件物品时,它有两种选择,放或者不放。

1) 如果第i件物品不放入背包中,那么问题就转换为:将前i - 1件物品放到容量为 j 的背包中,带来的收益dp[i - 1][ j ]

2) 如果第i件物品能放入背包中,那么问题就转换为:将前i - 1件物品放到容量为j - w[i]的背包中,带来的收益dp[i - 1][j - w[i]] + v[i]

扫描二维码关注公众号,回复: 3141019 查看本文章

Example

n = 4(物品数量) W=5(背包大小)

i(物品编号)

1 2 3 4
w(物品重量) 2 1 3 2
v(物品价值) 2 4 2

可以直接看出ans = 7,(选取编号为1,2,4三件物品,物品重量 :2 + 1 + 2,价值:3 + 2+ 2 或者 选取编号为1,3三件物品,物品重量 :2 + 3,价值:3 + 4 )

下面进行解释模拟一下各种状态之间是如何进行的(以加深自己它的理解):

模拟过程是第一层循环对物品(i:1->n)进行枚举,第二层循环是对背包容量(j:0->W)进行枚举。

运行结束后的dp数组
行为i,列为j 0 1 2 3 4 5
1 0 3 3 3 3
2 0 2 3 5 5 5
3 0 2 3 5 6 7
4 0 2 3 5 6 7

具体中间的数据如何计算,自己在脑中详细思考一下状态的转移就OK了!

Code(一)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int n = 4;//物品个数
const int W = 5;//背包最大容量
int w[n+1] = {0,2,1,3,2};//物品重量
int v[n+1] = {0,3,2,4,2};//物品价值
int dp[n+1][W+1];
int Knapsack()
{
	memset(dp,0,sizeof(dp)); //初始化
	//递推
	for(int i=1; i<=n; i++) //枚举物品
	{
		for(int j=0; j<=W; j++) //枚举背包容量
		{
			dp[i][j] = dp[i - 1][j];
			if (j >= w[i])
			{
				dp[i][j] = max(dp[i - 1][j],dp[i - 1][j - w[i]] + v[i]);
			}
		}
	}
	for(int i=1; i<=n; i++)
    {
        for(int j=0; j<=W; j++)
        {
            printf("%d ",dp[i][j]);
        }
        printf("\n");
    }
	return dp[n][W];
}
int main()
{
    cout<<Knapsack()<<endl;
    return 0;
}

效率:此算法的时间复杂度为O(nW),空间复杂度也为O(nW)。其中,n 表示物品个数,W 表示背包容量这里,时间复杂度不可以在优化了,但是空间复杂度可以继续优化到O(V)。

优化空间复杂度

上面的方法,我们使用二维数组 dp[i][j] 保存中间状态,可以使用一维数组f[j]保存中间状态就能得到结果.

我们现在使用 f[j] 保存中间状态,我们想要达到的效果是,第i次循环后,f[j]中存储的是前i个物体放到容量j时的最大价值

再回顾下之前讲过的状态转移方程:

dp[i][j] = max(dp[i - 1][j],dp[i - 1][j - w[i]] + v[i])

我们可以看到,要想得到 dp[i][j],我们需要知道 dp[i - 1][j] 和 dp[i - 1][j - w[i]],由于我们使用二维数组保存中间状态,所以可以直接取出这两个状态。

当我们使用一维数组存储状态时,f[j]表示,在执行i次循环后(此时已经处理i个物品),前i个物体放到容量v时的最大价值,即之前的dp[i][j]。与二维相比较,它把第一维隐去了,但是二者表达的含义还是相同的,只不过针对不同的i,dp[j]一直在重复使用,所以,也会出现第i次循环可能会覆盖第i - 1次循环的结果。

为了求dp[j],我们需要知道,前i - 1个物品放到容量v的背包中带来的收益,即之前的dp[i - 1][j]  和 前i - 1件物品放到容量为j - w[i]的背包中带来的收益,即之前的dp[i - 1][j - w[i]] + v[i]。

难点:由于我们只使用一维数组存储,则在求这两个子问题时就没有直接取出那么方便了,因为,第i次循环可能会覆盖第i - 1次循环的结果。

现在我们来求这两个值

1)前i - 1个物品放到容量v的背包中带来的收益,即之前的dp[i - 1][j] :

由于,在执行在i次循环时,dp[j]存储的是前i个物体放到容量v时的最大价值,在求前i个物体放到容量v时的最大价值(即之前的f[i][v])时,我们是正在执行第 i 次循环,dp[ j ]的值还是在第 i - 1  次循环时存下的值,在此时取出的 dp[ j ]就是前i - 1个物体放到容量v时的最大价值,即dp[i - 1][j]。

2)前i - 1件物品放到容量为j - w[i]的背包中带来的收益,即之前的dp[i - 1][j - w[i] ] + v[i]

由于,在执行第i次循环前,dp[0 -> V]中保存的是第i - 1次循环的结果,即是前i - 1个物体分别放到容量0 -> V时的最大价值,即dp[i - 1][0 -> V]。则,在执行第i次循环前,dp 数组中v - w[i]的位置存储就是我们要找的 前i - 1件物品放到容量为v - w[i]的背包中带来的收益 (即之前的dp[i - 1][v - weight[i]]),这里假设物品是从数组下标1开始存储的。

Code(二)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int n = 4;//物品个数
const int W = 5;//背包最大容量
int w[n+1] = {0,2,1,3,2};//物品重量
int v[n+1] = {0,3,2,4,2};//物品价值
int dp[W+1];

int Knapsack()
{
	memset(dp,0,sizeof(dp)); //初始化
	//递推
	for (int i = 1;i <= n;i++) //枚举物品
	{
		for (int j = W;j >= w[i];j--) //枚举背包容量,防越界,j下限为 weight[i]
		{
			dp[j] = max(dp[j],dp[j - w[i]] + v[i]);
		}
	}
	return dp[W];
}

int main()
{
	cout<<Knapsack()<<endl;
	return 1;
}
/*
目标:在不超过背包容量的情况下,最多能获得多少价值
子问题状态:dp[j]:表示前i件物品放入容量为j的背包得到的最大价值
状态转移方程:dp[j] = max{dp[j],dp[j - w[i]] + v[i]}
初始化:f数组全设置为0
*/

逆序枚举容量的原因:

注意一点,我们是由第 i - 1 次循环的两个状态推出 第 i 个状态的,而且 j  > j - w[i],则对于第i次循环,背包容量只有当V->0循环时,才会先处理背包容量为v的状况,后处理背包容量为 j-w[i] 的情况。

具体来说,由于,在执行v时,还没执行到j - w[i]的,因此,dp[j - w[i] ]保存的还是第i - 1次循环的结果。即在执行第i次循环 且 背包容量为j时,此时的dp[j]存储的是 dp[i - 1][j] ,此时dp[j-w[i]]存储的是dp[i - 1][j-w[i] ]。

相反,如果在执行第 i 次循环时,背包容量按照0->V的顺序遍历一遍,来检测第 i 件物品是否能放。此时在执行第i次循环 且 背包容量为v时,此时的dp[v]存储的是 dp[i - 1][j] ,但是,此时dp[j-w[i]]存储的是dp[i][j-w[i]]。

因为,j  > j - w[i],第i次循环中,执行背包容量为j时,容量为j - w[i]的背包已经计算过,即dp[j - w[i] ]中存储的是dp[i][j - w[i]]。即,对于01背包,按照增序枚举背包容量是不对的。

自己刚刚接触背包问题,对它的理解还不深刻,自己再多做点题,加深对背包问题和dp的理解吧!

附上一篇博客(日后查看):https://blog.csdn.net/insistgogo/article/details/8579597

猜你喜欢

转载自blog.csdn.net/Alibaba_lhl/article/details/81503453