01背包C++实现及其空间复杂度优化(个人理解)--逆序为什么能优化空间复杂度

文章是在观摩了博主xiajiawei0206《01背包问题 总结关于为什么01背包优化成1维数组后,内层循环是逆序的》的基础上进行撰写了,感谢博主提供。因此本文很多问题原型和数据原型便直接饮用博主的,不想自己花费时间再去琢磨。

博主xiajiawei0206博文连接:https://blog.csdn.net/xiajiawei0206/article/details/19933781

一、01背包问题原型

1、01背包问题的提出

有N件物品和一个容量为V的背包。第i件物品的重量是c[i],价值是w[i]。
求解将哪些物品装入背包可使这些物品的重量总和不超过背包容量,且价值总和最大。

(01背包中这些物品每种都只有1个,每个物品只能装一次) 

2、基本解决思路

这是最基础的背包问题,特点是:每种物品仅有一件,可以选择放或不放。

如果用F[i][v]表示第i件物品放入容量为v的背包里可以获得的最大价值,则该问题的递推表达式为:

F[i][v]=max{F[i-1][v],F[i-1][v-c[i]]+w[i]}                  (01背包问题的精髓所在)

表达式中的i为第i件物品,我们的物品是从i=1,2.....n进行排序递增的。

对该递推式子的理解为:如果我们考虑到第i件物品的时候,可以根据背包容量选择放或者不放,如果不放第i件物品,那么F[i][v]=F[i-1][v],即“第i件物品放入容量为v的背包里可以获得的最大价值”和“前i-1件物品放入容量为v的背包里可以获得的最大价值”是等同的;如果放置第i件物品,那么F[i][v]=F[i-1][v-c[i]]+w[i],即“第i件物品放入容量为v的背包里可以获得的最大价值”和“放置第i件物品后的剩余空间v-c[i]可以继续放置前i-1件物品的最大价值”。为了F[i][v]最大,所以取两者较大值。

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

3、基本解决思路C++实现

//空间复杂度为IV
void IV_ZEROONEPAC(int num,int *w,int *c,int dp[][MAX_V])
{
	for (int i = 1; i <= num; i++)
	{
		cout << "物品" << i << ":";
		cin >> c[i] >> w[i];
	}
	for (int i = 1; i <= num; i++)
	{
		for (int v = 0; v <= MAX_V; v++)//问题所在
		{
			if (v >= c[i])
				dp[i][v] = max(dp[i - 1][v], dp[i - 1][v - c[i]] + w[i]);
			else dp[i][v] = 0;
			cout << dp[i][v] << "\t";
		}
		cout << "\n";
	}
}

二、空间复杂度优化

1、优化原理

原式子(二维的):  f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]}-----------------------------式子(1)
现在要改成一维的(空间优化):     f[v]=max{ f[v],f[v-c[i]]+w[i]}--------------------式子(2)

注意上面的状态转移方程两边的是2个状态(左边的是这一状态  右边的是上一状态(二维的通过i可以看出来))

开始的时候始终不能明白式子(2),其实当我们i从1,2,3....n的时候,计算第i次f[v]时,等式(2)右值f[v]是上一次迭代(i-1次)产生的值(这句话是重点,明白了就能理解优化问题的本质了)。比如我们考虑一元函数g(x[i])=2g(x[i-1]),当我们i递增时,设初始值i=0,g(x0)=1,则:i=1,g(x[1])=2g(x[0])=2;i=2,g(x[2])=2g(x[1])=4,.........如果我们省略i不写,则:i=1,g(x)=2g(x)=2;i=2,g(x)=2g(x)=4,也就是说后面的g(x)是保存的上一次g(x)的值。

2、数据举例(用数学等式将每一步都写下来,离你理解01背包问题也不远了)

关于等式(2),开篇连接博主证明v顺序递增是不行的,大家可以去看看,会有启发的。下面的数据也是引用的原博主的,只是跟他用不同的思路去递推。

设有3件物品 ,背包能容纳的总重量为10,物品序号为i=1,2,3

物品号         重量(c)          价值(w)

i=1             4                 5

i=2             7                 9

i=3             5                 6

a、用式子(1)去递推(优化前)

当i=1时,v=4~10:

f[1][4]=max{f[0][4],f[0][0]+5}=5

f[1][5]=max{f[0][5],f[0][1]+5}=5

f[1][6]=max{f[0][6],f[0][2]+5}=5

f[1][7]=max{f[0][7],f[0][3]+5}=5

f[1][8]=max{f[0][8],f[0][4]+5}=5

f[1][9]=max{f[0][9],f[0][5]+5}=5

f[1][10]=max{f[0][10],f[0][6]+5}=5

当i=2时,v=7~10:

f[2][7]=max{f[1][7],f[1][0]+9}=9

f[2][8]=max{f[1][8],f[1][1]+9}=9

f[2][9]=max{f[1][9],f[1][2]+9}=9

f[2][10]=max{f[1][10],f[1][3]+9}=9

当i=3时,v=5~10:

f[3][5]=max{f[2][5],f[2][0]+8}=8

f[3][6]=max{f[2][6],f[2][1]+8}=8

f[3][7]=max{f[2][7],f[2][2]+8}=9

f[3][8]=max{f[2][8],f[2][3]+8}=9

f[3][9]=max{f[2][9],f[2][4]+8}=9

f[3][10]=max{f[2][10],f[5][3]+8}=9

b、用式子(2)去递推(优化后)

当i=1时,v=10~4:

f[10]=max{f[10],f[6]+5}=5(右式的f[10]之前没出现过,初始值为0)

f[9]=max{f[9],f[5]+5}=5

f[8]=max{f[8],f[4]+5}=5

f[7]=max{f[7],f[3]+5}=5

f[6]=max{f[6],f[2]+5}=5

f[5]=max{f[5],f[1]+5}=5

f[4]=max{f[4],f[0]+5}=5

当i=2时,v=10~7:

f[10]=max{f[10],f[3]+9}=9

f[9]=max{f[9],f[2]+9}=9

f[8]=max{f[8],f[1]+9}=9

f[7]=max{f[7],f[0]+9}=9

当i=3时,v=10~5:

f[10]=max{f[10],f[5]+8}=9

f[9]=max{f[9],f[4]+8}=9

f[8]=max{f[8],f[3]+8}=9

f[7]=max{f[7],f[2]+8}=9

f[6]=max{f[6],f[1]+8}=8

f[5]=max{f[5],f[0]+8}=8

网上的参考的一小段话:f[i][v]只与f[i-1][v]和f[i-1][v-C[i]]有关,即只和i-1时刻状态有关,所以我们只需要用一维数组f[]来保存i-1时的状态f[]。假设i-1时刻的f[]为{a0,a1,a2,…,av},难么i时刻的f[]中第v个应该为max(av,av-C[i]+W[i])即max(f[v],f[v-C[i]]+W[i]),这就需要我们遍历V时逆序遍历,这样才能保证求i时刻f[v]时f[v-C[i]]是i-1时刻的值。如果正序遍历则当求f[v]时其前面的f[0],f[1],…,f[v-1]都已经改变过,里面存的都不是i-1时刻的值,这样求f[v]时利用f[v-C[i]]必定是错的值。最后f[V]即为最大价值。

3、C++实现

//空间复杂度为V
void V_ZEROONEPAC(int num_v, int *w_v, int *c_v, int dp[])
{
	for (int i = 1; i <= num_v; i++)
	{
		cout << "物品" << i << ":";
		cin >> c_v[i] >> w_v[i];
	}
	for (int i = 1; i <= num_v; i++)
	{
		for (int v = MAX_V; v >= 0; v--)
		{
			if (v >= c_v[i]) dp[v] = max(dp[v], dp[v - c_v[i]] + w_v[i]);
			else dp[v] = 0;
			cout << dp[v] << "\t";
		}
		cout << "\n";
	} 
}

三、C++源代码

#include<iostream>
using namespace std;

#define MAX_V 10
int max(int a,int b)
{
	if (a > b) return a;
	else return b;
}
//空间复杂度为IV
void IV_ZEROONEPAC(int num,int *w,int *c,int dp[][MAX_V])
{
	for (int i = 1; i <= num; i++)
	{
		cout << "物品" << i << ":";
		cin >> c[i] >> w[i];
	}
	for (int i = 1; i <= num; i++)
	{
		for (int v = 0; v <= MAX_V; v++)//问题所在
		{
			if (v >= c[i])
				dp[i][v] = max(dp[i - 1][v], dp[i - 1][v - c[i]] + w[i]);
			else dp[i][v] = 0;
			cout << dp[i][v] << "\t";
		}
		cout << "\n";
	}
}

//空间复杂度为V
void V_ZEROONEPAC(int num_v, int *w_v, int *c_v, int dp[])
{
	for (int i = 1; i <= num_v; i++)
	{
		cout << "物品" << i << ":";
		cin >> c_v[i] >> w_v[i];
	}
	for (int i = 1; i <= num_v; i++)
	{
		for (int v = MAX_V; v >= 0; v--)
		{
			if (v >= c_v[i]) dp[v] = max(dp[v], dp[v - c_v[i]] + w_v[i]);
			else dp[v] = 0;
			cout << dp[v] << "\t";
		}
		cout << "\n";
	} 
}
int main()
{
	int num = 0;
	cout << "请输入物品件数:";
	cin >> num;
	int w[MAX_V] = { 0 };
	int c[MAX_V] = { 0 };
	int dq[MAX_V] = { 0 };
	int dp[MAX_V][MAX_V] = { { 0 }, { 0 } };
	IV_ZEROONEPAC(num, w, c, dp);
	cout << "==============================================================\n";
	V_ZEROONEPAC(num, w, c, dq);
	return 0;
}
//源代码中还有一个自己不能理解也不能解决的问题
//问题:当for (int v = 0; v <= MAX_V; v++)中v从1开始,则dp[3,0]=9,
//dp[3][5]=13,结果不对,讲道理,dp[0][0]=0。

猜你喜欢

转载自blog.csdn.net/qq_36327328/article/details/81163936