算法与数据结构-背包问题

原创 2012年03月21日 17:55:18

01背包问题

题目

有N件物品和一个容量为M的背包,每种物品只可以取一件。第i件物品的费用是c[i],价值是v[i]。求解将哪些物品装入背包可使价值总和最大。

分析

这是最基础的背包问题,特点是:每种物品仅有一件,可以选择放或不放。用子问题定义状态:即f[i][j]表示前i件物品恰放入一个容量为j的背包可以获得的最大价值。则其状态转移方程便是:

f[i][j]=max{f[i-1][j],f[i-1][j-c[i]]+v[i]}

优化空间复杂度

即改用一维数组f[j]存储第i个物品时剩余空间为j时的背包的最大价值。

注意到j-c[i]<j这个关系,当j=0……M顺序推f[j],则后面的到的f[j]将会使用到当前i状态下新生成的f[j-c[i]],而不是我们所需的i-1状态时的f[j-c[i]]。

因此,在每次主循环中我们以j=M……0顺序推f[j],这样才能保证推f[j]时f[j-c[i]]保存的是状态f[i-1][j-c[i]]的值。

参考代码

/*
 * n:物品种类 每种只能选取一种
 * capacity:背包容量
 * c[i]:第i种物品的花费 cost
 * v[i]:第i种物品的价值 value
 * f[j]:i状态下容量为j时背包可获得的最大价值
 */
int getMaxValue(int n,int capacity){
	for(int i=0;i<n;i++)
		for(int j=capacity;j>=0;j--)
			if(i==0){
				if(j>=c[i])f[j]=v[i];
				else f[j]=0;
			}
			else if(j>=c[i])f[j]=max(f[j],f[j-c[i]]+v[i]);
	return f[capacity];
}

 

完全背包问题

题目

有N件物品和一个容量为M的背包,每种物品都有无限件可用。第i件物品的费用是c[i],价值是v[i]。求解将哪些物品装入背包可使价值总和最大。

分析

类似01背包问题,但与它相关的策略已并非取或不取两种,而是有取0件、取1件、取2件……。如果仍然按照解01背包时的思路,令f[i][j]表示前i种物品恰放入一个容量为j的背包的最大权值。仍然可以按照每种物品不同的策略写出状态转移方程:

f[i][j]=max{ f[i-1][j-k*c[i]]+k*v[i] | 0<=k*c[i]<=M }

优化时间复杂度

若两件物品i、j满足c[i]<=c[j]且w[i]>=w[j],则将物品j去掉,不用考虑。

将费用大于M的物品去掉,然后使用类似计数排序的做法,计算出费用相同的物品中价值最高的是哪个。

转化为01背包问题求解

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

更高效的转化方法是:把第i种物品拆成费用为c[i]*2^k、价值为v[i]*2^k的若干件物品,其中k满足c[i]*2^k<=M。这是二进制的思想,因为不管最优策略选几件第i种物品,总可以表示成若干个2^k件物品的和。这样把每种物品拆成O(logM/c[i])件物品,是一个很大的改进。

最简O(MN)算法:首先想想为什么01背包问题中为何要按照j=M..0的逆序来循环。这是因为要保证第i次循环中的状态f[i][j]是由状态f[i-1][j-c[i]]递推而来。换句话说,这正是为了保证每件物品只选一次,保证在考虑“选入第i件物品”这件策略时,依据的是一个绝无已经选入第i件物品的子结果f[i-1][j-c[i]]。而现在完全背包的特点恰是每种物品可选无限件,所以在考虑“加选一件第i种物品”这种策略时,却正需要一个可能已选入第i种物品的子结果f[i][j-c[i]],所以就可以并且必须采用j=0..M的顺序循环。

参考代码

/*
 * n:物品种类 每种只能选取一种
 * capacity:背包容量
 * c[i]:第i种物品的花费 cost
 * v[i]:第i种物品的价值 value
 * f[j]:i状态下容量为j时背包可获得的最大价值
 */
int getMaxValue(int n,int capacity){
	for(int i=0;i<n;i++)
		for(int j=0;j<=capacity;j++)
			if(i==0){
				if(j>=c[i])f[j]=v[i]*(j/c[i]);
				else f[j]=0;
			}
			else if(j>=c[i])f[j]=max(f[j],f[j-c[i]]+v[i]);
	return f[capacity];
}



多重背包问题

题目

有N件物品和一个容量为M的背包,第i种物品最多有n[i]件可用。第i件物品的费用是c[i],价值是v[i]。求解将哪些物品装入背包可使价值总和最大。

分析

这题目和完全背包问题很类似。基本的方程只需将完全背包问题的方程略微一改即可,因为对于第i种物品有n[i]+1种策略:取0件,取1件……取n[i]件。令f[i][v]表示前i种物品恰放入一个容量为v的背包的最大权值,则有状态转移方程:
 f[i][j]=max{ f[i-1][j-k*c[i]]+k*v[i] | 0<=k<=n[i] }

优化时间复杂度

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

参考代码

/**
 * 多重背包决策:遍历到第i种物品(i状态)时的决策
 * cost:i状态时的花费c[i]
 * weight:i状态时的价值v[i]
 * amount:i状态时物品的最大数量n[i]
 * CompletePack:多重背包决策
 * ZeroOnePack:01背包决策
 */
void MultiplePack(cost,weight,amount){
	if(cost*amount>=capacity){
		CompletePack(cost,weight);return;
	}
	int k=1;
	while(k<amount){ 
		ZeroOnePack(k*cost,k*weight);
		amount=amount-k;
		k=k*2;
	}
	ZeroOnePack(amount*cost,amount*weight);
}


题目推荐:

ZOJ Problem Set - 1149 Dividing

http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=149

题意:

给出序号为1、2、3、4、5、6的6种弹珠(好像是弹珠吧⊙﹏⊙b汗),其价值等于其编号,给出每种弹珠的数目,求能否将弹珠分两堆,使两堆的价值和相等。

分析:

多重背包问题,只是所谓的花费cost等于价值,且背包容量为总和的一半。

代码:

#include <iostream>
#define MAX 210002
#define max(a,b) (a)>(b)?(a):(b)
using namespace std;
long nums[6],total,f[MAX];

int input(){
	for(int i=total=0;i<6;i++){
		cin>>nums[i];
		total+=nums[i]*(i+1);
	}
	if(total)return 1;
	return 0;
}

void zeroOnePack(int cost,int value,long capacity){
	for(int j=capacity;j>-1;j--)
		if(j>=cost)f[j]=max(f[j],f[j-cost]+value);
}
void completePack(int cost,int value,long capacity){
	for(int j=0;j<=capacity;j++)
		if(j>=cost)f[j]=max(f[j],f[j-cost]+value);
}
void multiplePack(int cost,int value,int count,long capacity){
	if(cost*count>=capacity){
		completePack(cost,value,capacity);
		return;
	}
	int k=1;
	while(k<count){
		zeroOnePack(cost*k,value*k,capacity);
		count-=k;
		k*=2;
	}
	zeroOnePack(cost*count,value*count,capacity);
}

int judge(long capacity){
	for(int i=0;i<6;i++){
		if(i==0){
			for(int j=0;j<=capacity;j++)
				if(j<i+1)f[j]=0;
				else{
					int amount=j/(i+1);
					f[j]=(nums[i]>=amount ? (i+1)*amount : nums[i]*(i+1));
				}
		}
		else multiplePack(i+1,i+1,nums[i],capacity);
	}
	if(f[capacity]==capacity)return 1;
	return 0;
}

int main()
{
	int T=1;
	while(input()){
		printf("Collection #%d:\n",T++);
		if(total&1)cout<<"Can't be divided."<<endl<<endl;
		else if(judge(total/2))cout<<"Can be divided."<<endl<<endl;
		else cout<<"Can't be divided."<<endl<<endl;
	}
	return 0;
}


 

基础数据结构--背包,队列,栈

抽象数据类型(ADT):泛指除基本数据类型以外的数据类型。什么叫类型?就是一类数据。基本数据类型被认做是最基本的,不可再划分的数据类型,一般就是整型、浮点型、以及字符型等。抽象数据类型是由若干基本数据...
  • TTCCAAA
  • TTCCAAA
  • 2015年04月15日 22:14
  • 2566

数据结构与算法——0-1背包问题

0-1背包问题
  • Linux_ever
  • Linux_ever
  • 2015年12月26日 22:20
  • 1168

数据结构 背包问题

  • 2017年10月07日 21:16
  • 8KB
  • 下载

数据结构经典算法学习之完全背包问题

(水)转载至:http://www.cnblogs.com/daoluanxiaozi/archive/2012/05/06/2486105.html
  • wang1472jian1110
  • wang1472jian1110
  • 2017年02月03日 17:36
  • 449

数据结构 与游戏背包的设计

数据结构分为:结构体、共用体、枚举型。 结构体:在定义的时候必须要写关键字struct  然后是结构体名,这个可写可不写 最后是结构体 例struct fun { 类型标示符  成员名; ...
  • zipp1995
  • zipp1995
  • 2016年08月15日 20:11
  • 903

数据结构作业之背包问题

问题及代码: 编写一个程序,求解背包问题:设有不同价值、不同重量的物品n件,求从这n件物品中选取一部分物品的方案,使选中物品的总重量不超过指定的限制重量,但选中物品的总价值最大。 /*烟台大学计算机学...
  • JYL1159131237
  • JYL1159131237
  • 2017年06月07日 21:43
  • 279

数据结构学习-递归(背包问题)

问题描述:设有不同价值,不同重量的物品n件,求从这n件物品中选取一部分物品的方案,使选中物品的总重量不超过指定的限制重量,但选中的物品总价值最大。代码:#include using namespace...
  • shope9
  • shope9
  • 2016年05月31日 17:02
  • 366

数据结构 简单背包问题

  • 2013年11月06日 23:03
  • 566B
  • 下载

[算法]数据结构算法背包问题解法之递归解法,C语言实现

今天讲背包问题的最后一种解法,递归解法,这种解法也是目前算法教材上讲的基本解法之一,如果你有一本关于这类算法的书籍,一般都可以找到你想要的算法,背包问题具体是什么,大家可以参考我的以前的文章,可以直接...
  • yctccg
  • yctccg
  • 2016年08月16日 10:39
  • 650

数据结构 与游戏背包的设计

数据结构分为:结构体、共用体、枚举型。 结构体:在定义的时候必须要写关键字struct  然后是结构体名,这个可写可不写 最后是结构体 例struct fun { 类型标示符  成员名; ...
  • zipp1995
  • zipp1995
  • 2016年08月15日 20:11
  • 903
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:算法与数据结构-背包问题
举报原因:
原因补充:

(最多只允许输入30个字)