动态规划——背包问题(一)01背包、完全背包

本次博客,我将记录自己对动态规划中背包问题的理解

01背包问题

首先问题如下:
有N件物品和一个容量为V的背包。第i件物品的质量是weight[i],价值是value[i]。求解将哪些物品装入背包可使这些物品的体积总和不超过背包容量,且价值总和最大

具体思路:
对于第n件物品,如果它装不进当前的背包,那就跳过,如果当前背包装得下,那么就有两条路:装,或者不装;

:总价值=前n-1件物品的总价值+第n件物品的价值
不装:总价值=前n-1件物品的总价值

接着,我们比较以上两种思路下物品的价值,并取总价值最高的思路

不妨举例,有如下4件物品

编号:1 2 3 4
质量:2 3 4 5
价值:3 4 5 6
背包总容量:8

对此,我列出表格

1

因此,列出状态转移方程:

f[i][j]=max(f[i-1][j],f[i-1][j-weight[i]]+value[i])

其中,f[i][v]表示选取前i件物品放入一个容量为v的背包可以获得的最大价值

此处我的理解是:对于第i件物品,max()中左值为不装该物品背包的总价值,右值表示装入该物品后背包物品的总价值,此处j-weight[i]就证实了这一点,接着我们比较二者,找出最大值

通过这样不断的比较,就可以得到背包容量为某个值时背包物品总价值的最大值,这样不断地用之前的最大值找到当前状态下价值的最大值,就可以找到答案
代码:

#include<iostream>
using namespace std;
int main()
{
	int weight[5]={0,2,3,4,5};
	int value[5]={0,3,4,5,6};
	int N=4,V=8;
	int f[5][9]={0};
	for(int i=1;i<=N;i++){
		for(int j=V;j>=0;j--){
			if(weight[i]>j){
				f[i][j]=f[i-1][j];
				continue;
			}
			f[i][j]=max(f[i-1][j],f[i-1][j-weight[i]]+value[i]);
		}
	}
	cout<<f[N][V];
	return 0;
}

当然,最后的输出结果为10

算法分析和优化:
该算法的时间复杂度和空间复杂度均为O(N*V),而空间复杂度可以进一步优化:

我们发现,当第i次循环时,我们的f[i][j]为第i件物品(也就是当前物品)装或不装后的最大价值,事实上,如果只保留一个一维数组f[v],在外循环第i次时,同样可以表示第i件物品装或不装后的最大价值

代码如下:

#include<iostream>
using namespace std;
int main()
{
	int weight[5]={0,2,3,4,5};
	int value[5]={0,3,4,5,6};
	int N=4,V=8;
	int f[9]={0};
	for(int i=1;i<=N;i++){
		for(int j=V;j>=0;j--){
			if(weight[i]>j){
				continue;
			}
			f[j]=max(f[j],f[j-weight[i]]+value[i]);
		}
	}
	cout<<f[V];
	return 0;
}

结果输出10


完全背包问题

问题如下:
有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的费用是weight[i],价值是 value[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大

与01背包的唯一不同就是,完全背包每件物品可以不限次拿

具体思路:
根据以上01背包的思路,这里我们直接给出状态方程再解释

f[i][j]=max(f[i-1][j],f[i-1][j-k*weight[i]]+k*value[i]),(k*weight[i]<=j)

很容易理解,因为每件物品都是无限个,因此对于第i件物品有k+1种方案,即不装和装k件,当然要注意方程后面k的范围

于是我们给出以下暴力代码:

for(int i=1;i<=N;i++){
	for(int j=V;j>=0;j--){
		for(int k=0;k<=j/weight[i];k++){
			f[i][j]=max(f[i-1][j],f[i-1][j-k*weight[i]]+value[i]);
		}
	}
}
cout<<f[N][V];

再根据01背包中的空间优化,我们可以这样写:

for(int i=1;i<=N;i++){
		for(int j=V;j>=0;j--){
			for(int k=0;k<=j/weight[i];k++){
				f[j]=max(f[j],f[j-k*weight[i]]+k*value[i]);
			}
		}
	}
	cout<<f[V];

我们还是这样举例,

编号:1 2 3 4
质量:2 3 4 5
价值:3 4 5 6
背包总容量:8

可以列出如下表格:

1

根据该表格,我们可以列出以下状态转移方程:

f[i][j]=max(f[i-1][j],f[i][j-weight[i]]+value[i]);

注意,此处max()里的右值中为f[i][j-weight[i]]+value[i],注意和01背包(f[j-weight[i]]+value[i])有所区别!

再进行空间优化:

f[j]=max(f[j],f[j-weight[i]]+value[i]);

(⊙o⊙)?,与01背包惊人的一致!!!

然而,我们可以发现,01背包问题的推导是逆向遍历,也就是说,f[j]用到的是上一条的旧数据(可以理解为利用了j-1时的最大价值分配法),但是完全背包却是顺向遍历,用的是新数据

因此,虽然二者在空间优化后的状态转移方程一样,但是由于顺序和逆序的问题,二者在最后代码上还是有所区别

完全背包代码:

#include<iostream>
using namespace std;
int main()
{
	int weight[5]={0,2,3,4,5};
	int value[5]={0,3,4,5,6};
	int N=4,V=8;
	int f[9]={0};
	for(int i=1;i<=N;i++){
		for(int j=0;j<=V;j++){ //注意此处顺向遍历,和01背包有所区别!
			if(weight[i]>j){
				continue;
			}
			f[j]=max(f[j],f[j-weight[i]]+value[i]);
		}
	}
	cout<<f[V];
	return 0;
}

结果输出12

关于文中时间、空间复杂度优化的补充,可以参考这篇文章:动态规划——背包问题(二)


本文完

posted @ 2022-07-18 18:34  Sky6634  阅读(106)  评论(0编辑  收藏  举报