背包问题 动态规划 JAVA

背包问题是动态规划的经典例题,最终的目的是使用一个空间有限的背包装下的物品的价值最大,即实现背包的最大效益。根据不同的条件限制,此类问题可以分为0_1背包问题,完全背包问题,多重背包问题。

1、0_1背包
问题:
给定一个空间为size的背包(PS:即背包能装下重量为size的物品),现在共有sum种物品,每种物品重量为weight、价值为value且有且仅有一件(PS:每个物品都是一个整体,不能进行拆分)。
输入:
一个整数sum(物品的种类)
sum行整数,每行有两个正整数weight、value(物品的重量和物品的价值)
一个整数size(背包的大小)
输出:
背包所能装下的物品的总价值。
输入样例:
3
10 10
20 25
30 10
45
输出样例:
35

分析:
动态规划的思想是将大规模的复杂的问题分解成一个个小规模的问题。
可能一次让你面对N种物品我们很难快速的做出是否要将此物品放入背包的选择,那么当我知道第N个物品的信息时如果知道前N-1个物品的情况下背包所能装下物品最大价值的话问题是不是会简单很多。
那么就从最简单的情况开始,假如只知道前一个物品(即只有一件物品待装入背包)。0_1背包需要先考虑背包的总容量是否能装下这个物品。
如果不能则背包的价值就是我知道0件物品时的价值,即money[1][size]=money[0][size](PS:二维数组money[i][j]的值是指当知道前i件物品、背包的容量为j时背包所能装下物品的最大价值)。
如果能则先将这件物品放入包内,则背包的价值增加了value[1],同时背包的大小减去weight[1]。那么问题就简化成已知背包的大小为size-value[1]、前0件物品待装入背包,求实现背包的最大效益。很明显第0件物品的大小和价值都为0,那么就得出了问题的解。
从上述最简单的情况进行推广,假如知道前i件物品(即有i件物品待装入背包),那么先考虑背包的总容量size是否能装下第i个物品。
如果不能装下第i个物品,就不用将第i个物品放在整个问题里考虑(因为即使背包是空的也不能装下第i件物品)。那么问题就变成了已经知道前i-1件物品,背包大小为size,求背包的最大效益,这个问题我们之前就已经解决过即money[i-1][size],所以可得到money[i][size]=money[i-1][size]。
如果能装下第i件物品那么先将第i件物品装进背包,那么背包的大小变为size-weight[i]、价值增加了value[i]。问题又转变为已知前 i-1件物品、背包大小为size-weight[i],求背包的最大效益,这个问题我们因该也已经解决了,即money[i-1][size-weigh]。但是背包的最大效益不一定就是money[i-1][size-weigh]+value[i],因为背包能装下第i件物品,我们可以选择装也可以选择不装,当不装时问题就是已知前i-1件物品、背包大小为size,实现背包的最大效益,这个问题我们也应该解决过了,即money[i-1][size]。回到原本的问题,易得背包的大小为
max(money[i-1][size],money[i-1][size-weigh]+value[i])。

代码如下:

import java.util.Scanner;

public class bag_for_0and1 {
  public static void main(String[] args) {
	int [] weight=new int[51];      // 物体重量
	int [] value=new int[101];      // 物体的价值
	
	Scanner scanner=new Scanner(System.in);
	int sum =scanner.nextInt();    //物品的总数
	for(int i=1;i<=sum;i++)  //输入物品i的重量以及价值
	{
		weight[i]=scanner.nextInt();
		value[i]=scanner.nextInt();
	}
	int size=scanner.nextInt();   //背包的大小
	int [][] money=new int[sum+1][size+1]; //值为这个背包所能装下的物品的价值的总和
	for(int i=1;i<sum+1;i++)  //假如只有前i个物品
	{
		for(int j=1;j<size+1;j++)  //假如背包的大小为j
		{
			if(j<weight[i])    //当背包总的空间小于第i个(即刚知道的物品)物品时
			{
				money[i][j]=money[i-1][j];   //背包只能在不超出背包大小的情况下装前i-1个物品并实现背包效益最大化
			}else {          //当背包总的空间能够装下第i个物品时		
  money[i][j]=money[i-1][j]>money[i-1][j-weight[i]]+value[i]?money[i-1][j]:money[i-1][j-weight[i]]+value[i];
	//尝试将第i个物品装进背包,那么背包剩余的空间还有j-weight[i]。再在不超出背包剩余容量的情况下装前i-1个物品并实现效益最大化。(即将问题的规模缩减了,但主要目的还是没有变)
    //判断,在装入第i个物品时的背包效益和不装入时的背包效益那个大?(即第i个物品在当前背包大小固定时到底值不值得装)
    //将二者相对效益高的写入money[i][j],即实现了当前已经知道的物品和已知的背包大小的条件下的背包效益最大化。
			}
		}
	}
	
	System.out.println(money[sum][size]);  //输出当知道所有物品时和确定背包的大小时,背包所能够装下的物品的价值的总和。
}
}

代码简化:
以上的代码采用的是二维数组,可以很容易就发现在求已知前i件物品、背包大小为size,实现背包最大效益的问题(即求money[i][size])时我们只可能用到二维数组中的money[i-1][0]……money[i-1][size]。这就提供了将二维数组换成一维数组的条件。
代码如下:

import java.util.Scanner;

public class bag_for_0and1_1wei {
   public static void main(String[] args) {
	   int [] weight=new int[51];      // 物体重量
		int [] value=new int[101];      // 物体的价值
		
		Scanner scanner=new Scanner(System.in);
		int sum =scanner.nextInt();    //物品的总数
		for(int i=1;i<=sum;i++)  //输入物品i的重量以及价值
		{
			weight[i]=scanner.nextInt();
			value[i]=scanner.nextInt();
		}
		int size=scanner.nextInt();   //背包的大小
		
		int money[]=new int[size+1];  //背包在空间有限的情况先所能实现的最大效益
		//这里可以将二维数组改为一维,是因为在二维数组中更新一行数据所需要的只是上一行的一部分数据作为参考。
		//例如:在更新money[2][4]时只可能用到money[1][0],money[1][1],money[1][2],money[1][3],money[1][4]
		//可见money[0][]对于我们来说是完全无用的,因此可以利用一维数组替换二维数组,这也解释了为什么一维数组要从数组末尾向数组首段更新。
		
		for(int i=1;i<sum+1;i++)   //假如只有前i个物品
		{
			for(int j=size;j>=1;j--) //注意:这里使用逆序的for循环是因为,对于数组来说,在更新money[x]时可能会用到money[0]……money[x-1]。所以如果在求money[x]之前,money[0]……money[x-1]就已经更新过了,会对结果造成影响。
			{
				if(j<weight[i])  //当背包总的空间小于第i个(即刚知道的物品)物品时
				{
					money[j]=money[j];  //背包只能在不超出背包大小的情况下装前i-1个物品并实现背包效益最大化
				}else {
					money[j]=money[j]>money[j-weight[i]]+value[i]?money[j]:money[j-weight[i]]+value[i];
					//如果先将第i个物品装进背包,那么背包剩余的空间还有j-weight[i]。再在不超出背包剩余容量的情况下装前i-1个物品并实现效益最大化。(即将问题的规模缩减了,但主要目的还是没有变)
				    //判断,在装入第i个物品时的背包效益和不装入时的背包效益那个大?(即第i个物品在当前背包大小固定时到底值不值得装)
				    //将二者相对效益高的写入money[i][j],即实现了当前已经知道的物品和已知的背包大小的条件下的背包效益最大化。
				}
			}
		}
		System.out.println(money[size]);  //输出当知道所有物品时和确定背包的大小时,背包所能够装下的物品的价值的总和。	
}
}

二、完全背包
问题:在0_1背包的基础上,将每种物品的数量由只有一件改为无限。

输入、输出要求同0_1背包。

输入样例:
3
10 10
20 25
30 10
45
输出样例:
50

分析:
不同于0_1背包只需要考虑放不放第i件物品,完全背包在0_1背包的基础上还要考虑放几件的问题。其实不妨将不装入第i件物品的情况视为放0件第i件物品。
这次我们直接从一般问题开始推导。假如已知第i件物品,我们首先考虑的是背包当前容量size是否能装下第i件物品。
如果不能,即能放下0件第i件物品,则问题转换为已知前i-1件物品、背包大小为size,求背包最大效益。这个问题的解我们应该是已经知晓的即money[i-1][size]。
如果只能放下一件第i件物品,则背包价值增加value[i]、重量减小weight[i]。问题就转化为已知前i-1件物品、背包大小为size-weight[i],求背包的最大效益。这个问题的解已经求过,即money[i-1][size-weight[i]]。如果 不放则背包最大效益为money[i-1][size]。则原本问题的解为max(money[i-1][size-weight[i]]+value[i],money[i-1][size])。
如果背包能放下两件第i件物品,则先放入第二一件第i件物品(此处说第二件第i件物品是为了方便理解,并不是说背包中已经放入过一件第i件物品),背包大小变为size-weight[i]、价值增加value[i]。然后问题就变成背包能放入一件第i件物品的问题,已知情况下背包最大效益为money[i][size-weight[i]](PS:至于到底放不放第i件物品是我们之前已经解决过的问题,所以在此不做考虑,只需将结果拿过来用)即背包的最大效益为money[i][size-weight[i]]+value[i]。再回到能放入两件第i件物品的问题,放入第二件第i中物品考虑过后,还要考虑不放第二件第i件物品的情况 :即money[i][size-weight[i]]+money[i][weight[i]],这明显是有问题的,因为money[i][size-weight[i]]和money[i][weight[i]]都不能保证将所有的空间都完全利用,但是二者的无效空间的和对于这个整体来说就可能是有效的了,因此这个思路是不对的。 那么换一个思路:首先思考一下为什么不放第二件第i种物品,因为其单位价值较其他的物品低,那么不放第二件物品是不是也意味着第一件第i种物品也没有必要放,即问题转换为欸背包大小为size、已知前i-1种物品的情况下背包的最大效益,易得为money[i-1][size]。
那么回到原本的问题,可以得出背包的最大效益为max(money[i][size-weight[i]]+value[i],money[i-1][size])。

代码如下:

import java.util.Scanner;

public class bag_for_wanquan_2wei {
 public static void main(String[] args) {
	 int [] weight=new int[51];      // 物体重量
		int [] value=new int[101];      // 物体的价值
		
		Scanner scanner=new Scanner(System.in);
		int sum =scanner.nextInt();    //物品的总数
		for(int i=1;i<=sum;i++)  //输入物品i的重量以及价值
		{
			weight[i]=scanner.nextInt();
			value[i]=scanner.nextInt();
		}
		int size=scanner.nextInt();   //背包的大小
		
		int money[][]=new int[sum+1][size+1];  //背包在空间有限的情况先所能实现的最大效益
		
		for(int i=1;i<sum+1;i++)
		{
			for(int j=1;j<size+1;j++)
			{
				if(j<weight[i])   //当背包总空间不能装下第i件物品时
				{
					money[i][j]=money[i-1][j];   
				}else {   //当背包总空间能装下第i件物品时
					money[i][j]=money[i-1][j]>money[i][j-weight[i]]+value[i]?money[i-1][j]:money[i][j-weight[i]]+value[i];
				}
			}
		}	
		System.out.println(money[sum][size]);
}
}

代码优化:
和0_1背包一样,完全背包也可以使用一维数组替代二维数组。但又与0_1背包有一点区别。在更新money[i][j]时可能用到的数据有money[i][0]……money[j-1]和money[i-1][j],因此可以将二维数组换成一维数组且为正序。

代码如下:

import java.util.Scanner;

public class bag_for_wanquan_1wei {
 public static void main(String[] args) {
	 int [] weight=new int[51];      // 物体重量
		int [] value=new int[101];      // 物体的价值
		
		Scanner scanner=new Scanner(System.in);
		int sum =scanner.nextInt();    //物品的总数
		for(int i=1;i<=sum;i++)  //输入物品i的重量以及价值
		{
			weight[i]=scanner.nextInt();
			value[i]=scanner.nextInt();
		}
		int size=scanner.nextInt();   //背包的大小
		
		int money[]=new int[size+1];  //背包在空间有限的情况先所能实现的最大效益
		
		for(int i=1;i<sum+1;i++)
		{
			for(int j=1;j<size+1;j++)
			{
				//这里没有采用逆序是因为,物品的数量是无限的。问题依然是实现背包的最大效益,但是因为物品数量的无限就使得可以将一种物品多次装入包内
				//此时比较的就不是前i-1种了而是i种物品,例如第一种物品的重量为4,价值为2.那么在只知道前1种物品时,背包空间又满足的情况下,我会装1、2、3、4……个第一种物品
				//当已知第二种物品的重量为5,价值为3时,在第一次背包满足的情况下我们会尝试背包中只放一个第二种物品,然后与只知第一种物品进行比较,将效益更高的方案保留,就得出了当前背包大小下的最优效益。
				if(j-weight[i]>=0)
				money[j]=money[j]>money[j-weight[i]]+value[i]?money[j]:money[j-weight[i]]+value[i];
			}
		}
		
		System.out.println(money[size]);
}
}

3、多重背包
问题:
在0_1背包的基础上,每种物品的数量都是有限的,即给定物品的数量。利用有限空间的背包装物品实现效益最大化。

输入:
一个整数sum(物品的种类)
sum行整数,每行有三个正整数weight、value、number(物品的重量、物品的价值和物品的数量)
一个整数size(背包的大小)
输出:
背包所能装下的物品的总价值。
输入样例:
3
10 10 3
20 25 2
30 10 5
35
输出样例:
35

分析:
在理解0_10背包的基础上我们可以很轻松的就将多重背包的问题解决。
多重背包与0_1背包的唯一区别就在于物品的数量不同。将多重背包问题中的物品进行拆分操作,例如第一件物品的重量为weight、价值为value、数量为2,我们可以将其拆分为两件重量为weight、价值为value、数量为1的物品,以此类推就很容易将多重背包问题转换成0_1背包的问题了。

0_1背包的问题已经说的很透彻了,这里就直接采用一位数组的方式。
代码如下:

import java.util.Scanner;

public class bag_for_duochong {
  public static void main(String[] args) {
	        
	        int [] weight=new int[51];      // 物体重量
			int [] value=new int[51];      // 物体的价值
			int [] number=new int[51];    //物体的数量
			
			Scanner scanner=new Scanner(System.in);
			int sum =scanner.nextInt();    //物品的总数
			for(int i=1;i<=sum;i++)  //输入物品i的重量、价值以及数量
			{
				weight[i]=scanner.nextInt();
				value[i]=scanner.nextInt();
				number[i]=scanner.nextInt();
				
			}
			
			int flag=sum+1;  //下标
			for(int i=1;i<sum+1;i++)
			{
				while(number[i]!=1)
				{
					weight[flag]=weight[i];
					value[flag]=value[i];
					number[i]--;
					flag++;
				}
			}
			
			int size=scanner.nextInt();   //背包的大小
			
			int money[]=new int[size+1];  //背包在空间有限的情况先所能实现的最大效益
			
			for(int i=1;i<flag;i++)   //假如只有前i个物品
			{
				for(int j=size;j>=1;j--)
				{
					if(j<weight[i])  //当背包总的空间小于第i个(即刚知道的物品)物品时
					{
						money[j]=money[j];  //背包只能在不超出背包大小的情况下装前i-1个物品并实现背包效益最大化
					}else {
						money[j]=money[j]>money[j-weight[i]]+value[i]?money[j]:money[j-weight[i]]+value[i];
						//如果先将第i个物品装进背包,那么背包剩余的空间还有j-weight[i]。再在不超出背包剩余容量的情况下装前i-1个物品并实现效益最大化。(即将问题的规模缩减了,但主要目的还是没有变)
					    //判断,在装入第i个物品时的背包效益和不装入时的背包效益那个大?(即第i个物品在当前背包大小固定时到底值不值得装)
					    //将二者相对效益高的写入money[i][j],即实现了当前已经知道的物品和已知的背包大小的条件下的背包效益最大化。
					}
				}
			}
			
			System.out.println(money[size]);  //输出当知道所有物品时和确定背包的大小时,背包所能够装下的物品的价值的总和。
}
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值