回溯——(装载/0-1背包)


搜索篇之回溯

(1)
0-1背包/装载

o-1背包

背包问题 :
已知一个容量大小为 M 重量的背包和 N 种物品,每种物品的重量为 Wi 。若将物品放入背包将得到 Pi 的效益,求怎样选取物品将得到效益最大

   输入:第一行一个整数,为背包的容量M;第二行一个整数,为物品的种数N;第三行N个整数为各物品的重量;第四行N个整数分别为N个物品的价值

   输出:第一行为最大总价值;第二行为装入的各物品的重量(未装的物品用0);第三行为装入的各物品的价值(未装的物品用0)


输入样例:
 
50
 3
 10 20 30
 60 100 120
输出样例:
 
220
 0 20 30
 0 100 120


解题思路:

对于每种可能的情况,进行判断,看是否是最优解;即对于每件物品,可以选择放(背包容量限制内),或是不放(不放的情况分别是当前背包放不下了,和如果这个不放,后面的可能使得value更大。)。然后对下一个物品进行判断,


解空间:

bag:array[1..maxn]of 0..1;

其中,bag[k]=1表示第k个物品要取

           0表示第k个物品不取


( K 表示第K个物品;N表示物品的个数)

约束条件:

背包足以放下当前物品;


状态树:

Best:=0;   表示最大价值
当一组解产生后,得到当前总价值count,
if count>best then best:=count
利用这种方法记录最优解!如果还要记录最优时的物品取法应:
if count>best then begin
    best:=count;
    for i:=1 to n do y[i]:=bag[i];




结束条件:

所有的可用的解法都已经搜索完毕,即K>=N时;


求最优解步骤:

对于每一种可能的解法,都与当前最优解比较,进行更新。

即:在K>=N的时候将当前的值与最优解比较,即current_value与best比较。进行更新;


注意:

回溯过程中注意状态的回复。



当然,如果仅仅是这样的话,很容易实现,但是耗时很大。原因是进行了很多不必要的搜索,那么心在就是要来剪枝了。


上界函数:当前值+剩余的物品的值<=当前最优解best

当前值指的是第k 层之前的值,剩余物品的值指的是不包括当前物品,剩余的值


if(curp+r>best)
{
<span style="white-space:pre">	</span>plag[k]=0;
	fun(k+1);
}

       即 当这个物品不放置的时候,即使后面的都能放进去,得到的值也小于最优值的时候,以此节点后的子树就军事不可行解。可以不予判断(即舍弃这个子树)。


源代码:


# include<stdio.h>
# include<string.h>
# include<stdlib.h>

# define M 1000

int w[M+100],p[M+100];
char plag[M+100],bag[+200]; 	//标记数组 
int r,best,curw,curp;			//剩余值,最优值,当前重量,当前值 
int n,m;

int fun(int k)
{
	int i;
	if(k>=n)
	{
		if(curp>best)			//更新操作 
		{
			for(i=0;i<n;i++) bag[i]=plag[i];
			best=curp;
		}
	}
	else
	{
		r-=p[k];
		if(curw+w[k]<=m)		//当前物品是否可放 
		{
			curw+=w[k];
			curp+=p[k];
			plag[k]=1;
			fun(k+1);
			curp-=p[k];			//状态恢复 
			curw-=w[k];
		}
		
		if(curp+r>best)			// 剪状态树枝 
		{
			plag[k]=0;
			fun(k+1);
		}
		r+=p[k];
	}
	return 0; 
}

int main(){
	
	int i;
	while(~scanf("%d",&m))
	{
		scanf("%d",&n);
		for(i=0;i<n;i++) 
		scanf("%d",&w[i]); 
		for(i=0;i<n;i++) 
		{
			scanf("%d",&p[i]);
			r+=p[i];
		}
		
		fun(0);
		
		printf("%d\n",best);
		for(i=0;i<n;i++) printf("%d ",bag[i]*w[i]); puts("");
		for(i=0;i<n;i++) printf("%d ",bag[i]*p[i]); puts("");
		
	}
	return 0;
}


装载问题,与此类似。


装载问题






解空间:子集树
可行性约束函数(选择当前元素):
上界函数(不选择当前元素):

当前载重量cw+剩余集装箱的重量r£当前最优载重量bestw

voidbacktrack(inti)

   {// 搜索第i层结点

      if (i > n) // 到达叶结点

      更新最优解bestx,bestw;return;

      r -= w[i];

      if (cw + w[i] <= c) {// 搜索左子树

         x[i] = 1;

         cw += w[i];

         backtrack(i+ 1);

         cw -= w[i];     }

      if (cw + r > bestw) {

         x[i] = 0; // 搜索右子树

         backtrack(i+ 1);      }

      r += w[i];

   }




源代码:


# include<stdio.h>
# include<string.h>
# include<stdlib.h>
#define M 1000

int w[M+100];
int n,bestw,curw,r;
int c1,c2;
char plag[M+100],bag[M+100];	//位置标记, 

int fun(int k)
{
	int i;
	if(k>=n)
	{
		if(curw>bestw)
		{
			for(i=0;i<n;i++)
			{
				bag[i]=plag[i];
			}
			bestw=curw;
		}
		
	}
	else
	{
		r-=w[k];
		if(curw+w[k]<=c1)
		{
			curw+=w[k],plag[k]=1;
			fun(k+1);
			curw-=w[k];		//状态恢复 
		}
		
		if(curw+r>bestw)	//上界函数(剪枝函数)  
		{
			plag[k]=0;
			fun(k+1);
		}
		r+=w[k];	//状态恢复 
	}
	return 0;
}

int main(){
	
	int i;
	while(~scanf("%d%d",&c1,&c2))
	{
		memset(plag,0,sizeof(plag));				
		scanf("%d",&n);
		for(i=0;i<n;i++)
		{
			scanf("%d",&w[i]);
			r+=w[i];
		}
		
		fun(0);
		printf("%d\n c1 -- ",bestw);
		for(i=0;i<n;i++)
		{
			printf("%d  ",bag[i]);
		}
		puts("");	
	}
	return 0;
}

编辑于2015/2/13- 01:38


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
回溯法是解决0-1背包问题的一种常用方法。该问题是指在给定n种物品和一个容量为C的背包的情况下,如何选择装入背包的物品,使得装入背包中物品的总价值最大。回溯法的基本思路是搜索所有可能的解,并在搜索过程中剪枝,以达到减少搜索次数的目的。具体实现可以参考引用中的递归函数rKnap。 在回溯法中,我们首先将物品按照单位重量的价值递减排序,然后从第一个物品开始搜索。对于每个物品,我们有两种选择:将其放入背包或不放入背包。如果将其放入背包,我们需要检查当前背包容量是否足够,如果足够,则将其放入背包,并更新当前背包的重量和价值。然后递归搜索下一个物品。如果不将其放入背包,则直接递归搜索下一个物品。在搜索过程中,我们需要记录当前背包的重量和价值,以及当前最优解的最大价值。如果当前背包的价值已经超过当前最优解的最大价值,则可以剪枝,不再继续搜索。 C++代码实现可以参考以下范例: <<范例: #include <iostream> #include <algorithm> using namespace std; const int MAXN = 100; int n, c; int w[MAXN], v[MAXN]; int bestv = 0, curv = 0, curw = 0; void backtrack(int i) { if (i > n) { bestv = max(bestv, curv); return; } if (curw + w[i] <= c) { curw += w[i]; curv += v[i]; backtrack(i + 1); curw -= w[i]; curv -= v[i]; } if (curv + v[i] * (c - curw) > bestv) { backtrack(i + 1); } } int main() { cin >> n >> c; for (int i = 1; i <= n; i++) { cin >> w[i] >> v[i]; } sort(w + 1, w + n + 1); sort(v + 1, v + n + 1); backtrack(1); cout << bestv << endl; return 0; } >>

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值