我眼中的--背包问题

背包问题

背包问题可以描述成:给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高;

一.基础背包(01背包)

1.特点

这种背包问题的特点就是:每种物品限定只有一个,选择就只有两种,放(1)不放(0);

2.思路

(1)对于每个物品就只有放与不放两种状态
假如第一件物品不放,考虑的问题就变成了背包总重量与第二件物品到第i件物品放不放的问题;
假如第一件物品放,那么背包的空间就会变成总背包重-第一件物品重,考虑的问题也就变成了剩下的背包重量与第二件物品到第i件的放与不放的问题;
所以这道题就能分解成第i件物品到底放不放的问题。
(2)影响放不放的因素就是值不值得放,也就是价格
因为要在一定的背包重量里面组合出最高的价格,所以单纯的价格/重量来决定值不值得放是不可取的;所以为了判断放不放,提出的解决方案就是–填表,填表来列举每一个选择产生的价值总量是多少;
例如有5个物品,背包重量是10,每个物品的重量和价格如下:

重量 价格
7 9
2 2
2 7
5 6
9 9

填表过程如下:

重量 价格 背包空间/0 1 2 3 4 5 6 7 8 9 10
初始化 0 0 0 0 0 0 0 0 0 0 0
7 9 0 0 0 0 0 0 0 9 9 9 9
2 2 0 0 2 2 2 2 2 9 9 11 11
2 7 0 0 7 7 9 9 9 9 9 16 16
5 6 0 0 7 7 9 9 9 13 13 16 16
9 9 0 0 7 7 9 9 9 13 13 16 16

而至于为什么这么填表:
1.第一个物品(7,9),因为他的物品重量是7,所以在背包空间0-6的时候都没办法放下,在7的时候,因为这个时候背包里面的价格比第一个物品小,所以放第一个物品,这个时候背包的最大价值为9;
2.第二个物品(2,2),因为重量是2,所以在背包空间达到2的时候就可以放进去,这个时候背包里面的价格为0,2>0,值得放。
当背包空间达到7的时候,背包里面的价格在上一次筛选已经变成了9,9>2,所以背包空间达到7的时候我们选择放第一个物品进去,第二个物品不放;
当背包空间达到9的时候,背包可以都容下第一个物品和第二个物品,9+2=11,目前背包里面的价格最大就是11;
3.剩下的依次类推
最终最后一个数就会达到所求最大。
(3)填表里面最重要的也就是所谓的转移代码:

dp[i][j]=max{dp[i-1][j],dp[i-1][j-w[i]]+v[i]}
		//转移方程

在第i次做选择时,判断放与不放谁的价格更大,取最大的来做决定。

3.整体代码

1.待优化:

#include <stdio.h>
#include <string.h>
#define N 100
int max(int a,int b)
{
	return a>=b?a:b;
}
	//比较大小
int main()
{
	int w[N],v[N];
	int n,h;
	int dp[N][N];
	memset(dp,0,sizeof(dp));
		//dp数组赋值为0;
	scanf("%d %d",&n,&h);
		//输入n个物品,背包重量为h
	for(int i=1;i<=n;i++)
		{
			scanf("%d %d",&w[i],&v[i]);
		}
		//输入第i个物品重量w[i],价格v[i];
	for(int i=1;i<=n;i++)
		for(int j=0;j<=h;j++)
			{
			if(j<w[i])
				dp[i][j]=dp[i-1][j];
			else
				dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);
			}
			//转移方程
	/*for(int i=0;i<=n;i++)
	{
		for(int j=0;j<=h;j++)
		printf("%d   ",dp[i][j]);
		printf("\n");
	}*/
		//这里是可以输出上面那个填表,验证猜想;
	printf("%d",dp[n][h]);
		//最大,即二维数组最后一位
	return 0;
}

2.可以压缩空间,转移方程从二维数组简写成一维数组:
压缩空间的原理是:只记录第i-1件物品放入时的背包状态,当第i件物品放入的时候调用和覆盖,形成新的数据,所以就只需要一维数组来表示;

dp[j]=max{dp[j],dp[j-w[i]]+v[i]}

完整转移方程如下:

for(int i=1;i<=n;i++)
		for(int j=h;j>=0;j--)
			{
			if(j>w[i])
				dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
			}

二.完全背包

1.特点

在基础背包的基础上,每件物品从只有一个变成了无限件。这个时候每件物品考虑从放与不放变成了不放和放几个的问题。

2.思路

(1)完全背包和基础背包特别相似,所以解题思路也可以从中体会,引用搜狗百科上面的一句话:

最简单的想法是,考虑到第i种物品最多选V/c 件,于是可以把第i种物品转化为V/c件体积及价值均不变的物品,然后求解这个01背包问题。
这样完全没有改进基本思路的时间复杂度,但这毕竟给了我们将完全背包问题转化为01背包问题的思路:将一种物品拆成多件物品。

(2)将一种物品拆成多件物品,只需要在01背包的代码基础上稍加修改即可,填表的原理还是一样的;
代码如下:

for(int i=1;i<=n;i++)
 for(int j=0;j<=h;j++)
  for(int k=0;k*w[i]<=j;k++)
 dp[i][j]=max(dp[i-1][j],dp[i-1][j-k*w[i]]+k*v[i]);
 //01背包的转移方程修改

但是这种情况下是三重for循环,时间复杂度太高了,所以,就提出了简化方案;
(3)调整max函数里的比较对象
因为01背包里面的转移代码都是和第i-1个物品进行比较,所以在完全背包里面,我们可以把这个比较对象换成第i-1个物品在当前背包空间的价格和当前这个第i个物品上一次选择的价格进行比较;
代码如下:

for(int i=1;i<=n;i++)
 for(int j=0;j<=h;j++)
   if(j<w[i]) 
     dp[i][j]=dp[i-1][j];
   else 
     dp[i][j]=max(dp[i-1][j],dp[i][j-w[i]]+v[i]);

(4)同01背包一样,完全背包也可以压缩空间,简化代码;

for(int i=1;i<=n;i++)
 for(int j=0;j<=h;j++)
   if(j>=w[i])
     dp[j]=max(dp[j],dp[j-w[i]]+v[i]);

3.整体代码

#include <stdio.h>
#include <string.h>
#define N 100
int max(int a,int b)
{
	return a>=b?a:b;
}

int main()
{
	int w[N],v[N];
	int n,h;
	int dp[N];
	memset(dp,0,sizeof(dp));
	scanf("%d %d",&n,&h);
	for(int i=1;i<=n;i++)
		{
			scanf("%d %d",&w[i],&v[i]);
		}
 for(int i=1;i<=n;i++)
 	for(int j=0;j<=h;j++)
   		if(j>=w[i])
     		dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
/*for(int j=0;j<=h;j++)
		printf("%d   ",dp[j]);
		printf("\n");*/
	printf("%d",dp[h]);
	//一维数组最后一个,即为背包最大价格
	return 0;
}

三.多重背包

1.特点

每一件物品不是只有一件或者无限件,而是只有有限个数目的背包问题;

2.思路

(1)想法是在完全背包的基础上稍加修改,把无限个换成有限个就可以了;
代码如下:

for(int i=1;i<=n;i++)
 for(int j=0;j<=h;j++)
  for(int k=0;k<=m[i]&&k*w[i]<=j;k++)
 dp[i][j]=max(dp[i-1][j],dp[i-1][j-k*w[i]]+k*v[i]);
 //m数组就是每个物品有多少个;

(2)改进方法

方法是:将第i种物品分成若干件物品,其中每件物品有一个系数,这件物品的费用和价值均是原来的费用和价值乘以这个系数。使这些系数分别为 1,2,4,…,2(k-1),n-2k+1,且k是满足n-2^k+1>0的最大整数。例如,如果n为13,就将这种物品分成系数分别为1,2,4,6的四件物品。
分成的这几件物品的系数和为n,表明不可能取多于n件的第i种物品。

即就是把单件物品二进制分解成不同件物品,在进行背包问题。
多重背包有以下两种情况
1.如果数量x物品重量>=背包重量
2.如果数量x物品重量<背包重量
只有当情况二的时候才当做多重背包来做,进行二进制分解;
当情况一的时候,可以当完全背包来使用。

四.总结

背包问题实际上都差不多,都是在01背包的基础上发展的,三种背包的大体形式都差不多,只需要在转移方程的时候做些改变就好了。这是小杨这两天的背包总结,到处百度,然后又把学校里面学的拿出来总结,终于完成咯。
这是搜狗百科对于背包问题的词条:背包问题

发布了12 篇原创文章 · 获赞 7 · 访问量 1457

猜你喜欢

转载自blog.csdn.net/weixin_45843077/article/details/104154943
今日推荐