【回溯算法】0-1背包问题

【问题描述】

给定一个物品集合s={1,2,3,…,n},物品i的重量是wi,其价值是vi,背包的容量为W,即最大载重量不超过W。在限定的总重量W内,我们如何选择物品,才能使得物品的总价值最大。

输入

第一个数据是背包的容量为c(1≤c≤1500),第二个数据是物品的数量为n(1≤n≤50)。

接下来n行是物品i的重量是wi,其价值为vi。

所有的数据全部为整数,且保证输入数据中物品的总重量大于背包的容量。

输出

对每组测试数据,输出装入背包中物品的最大价值。

输入样例

输出样例

50 3

10 60

30 120

20 100

220

【算法分析】 

对于有n种可选择物品的0—1背包问题,其解空间由长度为n的0—1向量组成,该解空间包含了对变量的所有可能的0—1赋值。

假设背包容量C=30,w={16,15,15},v={45,25,25}

(1) 令cw(i)表示目前搜索到第i层已经装入背包的物品总重量,即部分解(x1, x2 , …, xi)的重量:

 

(2)对于左子树, xi =1 ,其约束函数为:  

(3)若constraint(i)>W,则停止搜索左子树,否则继续搜索。

 (4)对于右子树,为了提高搜索效率,采用上界函数Bound(i)剪枝。

cv(i)表示目前到第i层结点已经装入背包的物品价值:

 

r(i)表示剩余物品的总价值:

 

则限界函数Bound(i)为:

 假设当前最优值为bestv,若Bound(i)<bestv,则停止搜索第i层结点及其子树,否则继续搜索。

显然r(i)越小, Bound(i)越小,剪掉的分支就越多。

为了构造更小的r(i) ,将物品以单位重量价值比di=vi/wi递减的顺序进行排列:    d1≥d2≥… ≥dn

对于第i层,背包的剩余容量为W-cw(i),采用贪心算法把剩余的物品放进背包,根据贪心算法理论,此时剩余物品的价值已经是最优的,因为对剩余物品不存在比上述贪心装载方案更优的方案。

 【代码部分】

 

//0-1背包问题回溯算法的数据结构

#define NUM 100
int c;				//背包的容量
int n;				//物品的数量
int cw;				//当前重量
int cv;				//当前价值
int bestv;			//当前最优价值
//描述每个物品的数据结构
struct Object{
	int w;			//物品的重量
	int v;			//物品的价值
	double d;		//物品的单位重量价值比
}Q[NUM];			//物品的数组

 

//对物品以单位重量价值比递减排序的因子是:
bool cmp(Object a, Object b)
{
	if(a.d>=b.d) return true;
	else return false;
}


//物品的单位重量价值比是在输入数据时计算的:
for(int i=0; i<n; i++)
{
	scanf("%d%d",&Q[i].w,&Q[i].v);
	Q[i].d = 1.0*Q[i].v/Q[i].w;
}


//使用C++标准模板库的排序函数sort()排序:
sort(Q, Q+n, cmp);
//0-1背包问题回溯算法的实现

//形参i是回溯的深度,从0开始
void backtrack(int i)
{
  //到达叶子结点时,更新最优值
  if (i+1>n) {bestv = cv; return;}
  //进入左子树搜索
  if (cw+Q[i].w<=c)
  {
    cw += Q[i].w;
    cv += Q[i].v;
    backtrack(i+1);
    cw -= Q[i].w;
    cv -= Q[i].v;
  }
  //进入右子树搜索
  if (Bound(i+1)>bestv) backtrack(i+1);
}

 

//限界函数Bound()的实现

//形参i是回溯的深度
int Bound(int i)
{
  int cleft = c-cw;   //背包剩余的容量
  int b = cv;      //上界
  //尽量装满背包
  while (i<n && Q[i].w<=cleft)
  {
    cleft -= Q[i].w;
    b += Q[i].v;
    i++;
  }
  //剩余的部分空间也装满
  if (i<n) b += 1.0*cleft*Q[i].v/Q[i].w;
  return b;
}
#include<iostream>
#include<algorithm>
using namespace std;

#define NUM 100
int c;
int n;
int cw;
int cv;
int bestv;

struct Object{
	int w;
	int v;
	double d;
}Q[NUM];

bool cmp(Object a, Object b)
{
	if(a.d>=b.d) return true;
	else return false;
}

int Bound(int i)
{
	int cleft = c-cw;
	int b = cv;
	while (i<n && Q[i].w<=cleft)
	{
		cleft -= Q[i].w;
		b += Q[i].v;
		i++;
	}
	if (i<n) b += 1.0*cleft*Q[i].v/Q[i].w;
	return b;
}

void backtrack(int i)
{
	if (i+1>n) {bestv = cv; return;}
	if (cw+Q[i].w<=c)
	{
		cw += Q[i].w;
		cv += Q[i].v;
		backtrack(i+1);
		cw -= Q[i].w;
		cv -= Q[i].v;
	}
	if (Bound(i+1)>bestv) backtrack(i+1);
}

int main()
{
	while(cin>>c)
	{
		cw = 0;
		cv = 0;
		bestv = 0;
		cin>>n;
		for(int i=0; i<n; i++)
		{
			cin>>Q[i].w>>Q[i].v;
			Q[i].d = 1.0*Q[i].v/Q[i].w;
		}
		sort(Q, Q+n, cmp);
		backtrack(0);
		cout<<bestv;
	
	}
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值