01背包(DP、回溯、分支限界)

问题描述:

有n 个物品,它们有各自的重量和价值,现有给定容量c的背包,如何让背包里装入的物品具有最大的价值?

思路分析:

一、动态规划:

1、把背包问题抽象化(X1,X2,…,Xn,其中 Xi 取0或1,表示第 i 个物品选或不选),Vi表示第 i 个物品的价值,Wi表示第 i 个物品的体积(重量);    

2、建立模型,即求max(V1X1+V2X2+…+VnXn);

3、约束条件,W1X1+W2X2+…+WnXn<c;

4、定义m(i,j):当前背包容量 j,前 i 个物品最佳组合对应的价值;

5、寻找递推关系式,面对当前商品有两种可能性:

第一,背包的容量比该商品体积小,装不下,此时的价值与前i-1个的价值是一样的,即V(i,j)=V(i-1,j);

第二,还有足够的容量可以装该商品,但装了也不一定达到当前最优价值,所以在装与不装之间选择最优的一个,即V(i,j)=max{V(i-1,j),V(i-1,j-w(i))+v(i) },其中V(i-1,j)表示不装,V(i-1,j-w(i))+v(i) 表示装了第i个商品,背包容量减少w(i)但价值增加了v(i);

由此可以得出递推关系式:

    1) j<w(i)      V(i,j)=V(i-1,j)

    2) j>=w(i)     V(i,j)=max{V(i-1,j),V(i-1,j-w(i))+v(i)}

 

二、回溯法:

回溯法需要构造解的子集树。对于每一个物品i,对于该物品只有选与不选2个决策,总共有n个物品,可以顺序依次考虑每个物品,这样就形成了一棵解空间树,基本思想就是遍历这棵树,以枚举所有情况,最后进行判断,如果重量不超过背包容量,且价值最大的话,该方案就是最后的答案。在搜索状态空间树时,只要左子节点是可一个可行结点,搜索就进入其左子树。对于右子树,先计算上界函数,以判断是否将其减去(剪枝)。上界函数bound():当前价值cw+剩余容量可容纳的最大价值<=当前最优价值bestp。

为了更好地计算和运用上界函数剪枝,选择先将物品按照其单位重量价值从大到小排序,此后就按照顺序考虑各个物品。

 

三、分支限界法:

采用优先队列求解该问题,活结点队列中的结点结点元素N的 优先级由结点的上界函数Bound计算出的uprofit确定。算法中,E 是当前扩展结点,cw是该节点的重量,cp是其价值,up是价值上界。通过循环不断扩展结点,直到子集树的叶节点成为扩展结点,此时优先队列中所有活结点的价值上界都不超过该叶节点的价值。因此该结点相应的解为问题的最优解。

先检查扩展结点的左孩子结点的可行性,可行就加入活结点队列和子集树中。当右孩子结点满足上界约束将其加入子集树和活结点队列。

代码:

1、动态规划

void knapsack(int *v, int *w, int c, int n, int **m){
			int jMax = min(w[n]-1,c);
			for(int j = 0; j<=jMax; j++){
				m[n][j] = 0;
			}
			for(int j = w[n]; j<=c; j++){
				m[n][j] = v[n];
			}
			for(int i = n-1; i>1; --i){
				jMax = min(w[i]-1,c);
				for(int j = 0; j<=jMax; j++){
					m[i][j] = m[i+1][j];
				}
				for(int j = w[n]; j<=c; j++){
					m[i][j] = max(m[i+1][j],m[i+1][j-w[i]]+v[i]);
				}
			}
			m[1][c] = m[2][c];
			if(c>=w[1]){
				m[1][c] = max(m[2][c],m[2][c-w[1]]+v[1]);
			}
}

void TraceBack(int *x, int *w, int c, int n, int **m){
			for(int i = 1; i<n; i++){
				if(m[i][c] == m[i+1][c]){
					x[i] = 0;
				}else{
					x[i] = 1;
					c-=w[i];
				}
			}
			x[n] = (m[n][c]) ? 1:0;
}

2、回溯

int cw = 0;        //当前背包中物品的重量
int cp = 0;        //当前背包中物品的价值
int bestp = 0;     //当前的最优解
int *w,
    		*p, 
    		*bestx,
			n,
	 		c;

//限界函数
int Bound(int i){
	int left = c-cw;
	int b = cp;
	while(i <= n && w[i] <= left){
		left -= w[i];
		b += p[i];
		i++;
	}
	if(i<=n) b += p[i]*left/w[i];
	return b;
}

void BackTrack(int i){
			if(i>n){
				bestp = cp;
				return;
			}          
			if(cw+w[i] <= c){
				cw += w[i];
				cp += p[i];
				bestx[i] = 1;
				BackTrack(i+1);
				cw -= w[i];
				cp -= p[i];
			}
			if(Bound(i+1) > bestp) {
				bestx[i] = 0;
				BackTrack(i+1);
			}
}

3、分支限界

struct bbnode{
	        bbnode *parent;
	        int LChild;
};

//加入队列的节点
struct Node{
	operator int () const {return uprofit;}
	float uprofit;
	int profit;
	int weight;
	int level;
	bbnode *ptr;
};

//优先队列
priority_queue<Node> Q;
bbnode *E;
int c;
int n;
int *w;
int *p;
int cw;
int cp;
int *bestx;

//限界函数
float Bound(int i){
	int cleft = c-cw;
	float b = cp;
	while(i<=n && w[i]<=cleft){
		cleft -= w[i];
		b += p[i];
		i++;
	}
	if(i<=n) b += 1.0* p[i]/w[i]*cleft;
	return b;
}

void AddLiveNode(int up, int cp, int cw, int ch, int lev){
	bbnode *b = new bbnode;
	b->parent = E;
	b->LChild = ch;
	Node N;
	N.uprofit = up;
	N.profit = cp;
	N.weight = cw;
	N.level = lev;
	N.ptr = b;
	Q.push(N);
}

int MaxKnapsack(){
	bestx = new int [n+1];
	int i = 1;
	E = NULL;
	cw = cp = 0;
	int bestp = 0;
	float up = Bound(1);
	while(i != n+1){
		int wt = cw + w[i];
		if(wt<=c){
			if(cp+p[i]>bestp) bestp = cp+p[i];
			AddLiveNode(up, cp+p[i], cw+w[i], 1, i+1);
		}
		up = Bound(i+1);
		if(up>=bestp) AddLiveNode(up, cp, cw, 0, i+1);
		Node N = Q.top(); Q.pop();
		E = N.ptr;
		cw = N.weight;
		cp = N.profit;
		up = N.uprofit;
		i = N.level;
	}
	
	for(int j=n; j>0; --j){
		bestx[j] = E->LChild;
		E = E->parent;
	}
	return cp;
}

 

运行结果:

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值