回溯法 | 求解装载问题

问题描述:

有 n 个集装箱要装上一艘载重量为 W 的轮船,其中集装箱 i (1<=i<=n) 的重量,为wi。子啊装在体积不受限制的情况下,将尽可能重的集装箱装上轮船,当重量相同时要求选取的集装箱个数尽可能少,编写一个实验程序采用回溯法求解。要求采用适当的剪枝条件提高效率,左孩子结点剪枝的条件是只装载满足重量要求的集装箱,右孩子结点剪枝的条件是至多要选中 3 个集装箱。

例如,n=5,W=10,w[] = {5,2,6,4,3}时,其最佳装载方案是(0,0,1,1,0),既装载第3、4个集装箱。

问题分析:

看完题目感觉可以用穷举法把所有可能性全列举出来,然后从所有结果中选取一个最优解,然后输出。

but,题目需要回溯法,那么就要考虑用回溯法的思想去解决这个问题。

回溯法:在包含问题的所有解的解空间树中,按照深度搜索优先的策略,从根节点出发搜索解空间树。

下图展示的是程序的大致执行过程。

简单的说就是一直在深度优先搜索结果,查找完当前层的时候需要恢复以前修改数据,保证接下来深度搜索基准数据没有错。

如果只是简单的回溯,其实和穷举法差不多,但是 回溯法中可以写剪枝函数,把那些不满足条件的枝叶剪掉,从而加快搜索速度。

下面我们来模拟一遍回溯的过程,假设现在w[] 这个数组中只有3个元素,分别为 5 、2、6

图中黑色直线标识深度递归到某一层进行计算,蓝色的线条表示当前层计算完毕,需要恢复数据到上一层接着进行寻找。

每个节点右两个值,第一个代表当前层装的数量,第二个代表当前层的重量.

咱们设定取左孩子表示装当前 i 集装箱,取右孩子表示不装当前 i 集装箱

#include <stdio.h>

#define MAXN 20

int maxw=0;	//总重量
int min=65535;	//装的最少数量
int res[MAXN];	//存放最终结果
int count=0;	//测试执行次数 

int getMax(int a,int b)
{//获取两个数中最大值
	return a>b?a:b;
}

void cpsz(int a[],int b[],int n)
{//复制数组
	int i;
	for(i=0;i<n;i++){
		b[i]=a[i];
	}
}

// i 表示层数 
// n 表示总层数
// w[] 存放每个物品重量
// W 表示最大重量
// tw 当前层的重量
// tc 当前层的数量
// opt[] 临时存放解
void f(int i,int n,int w[],int W,int tw,int tc,int opt[])
{//主要递归主体
	count++;
	printf("层数 %d 总层数 %d 最大重量 %d 当前层重量 %d 当前层数量 %d count: %d \n",i,n,W,tw,tc,count);
	if(i<n){
		//取 i 物品 左剪枝
		if(tw+w[i]<=W){
			opt[i]=1;
			f(i+1,n,w,W,tw+w[i],tc+1,opt);
		}
		opt[i]=0;
		f(i+1,n,w,W,tw,tc,opt);
	}else{
		if(tw>=maxw){
			maxw=tw;
			if(tc<min){
				cpsz(opt,res,n);
				min=tc;
			}
		}
	}
}

void printRes(int w[],int n){
	int i;
	printf("问题最终解为\n装箱物品: ");
	for(i=0;i<n;i++){
		if(res[i]==1)
			printf("%d ",w[i]);
	}
	printf("\n装箱最大的容量为:%d",maxw);
	printf("\n装箱数量为%d\n",min);
}

int main()
{
	int n=5;	//n个集装箱
	int W=10;	//轮船最大载重量
	int w[] = {9,4,8,6,1};	//n个物品对应的数量
	int opt[MAXN];	//存放临时解
	f(0,n,w,W,0,0,opt);
	printRes(w,n);
	return 0;
}

从执行结果可以看出来左剪枝还是起到一定效果,高度为 5 的满二叉树应该为 31 次,但实际执行了27次.

由于题上给出来右剪枝的条件,所以可以进行右剪枝.下面就是右剪枝的代码

#include <stdio.h>

#define MAXN 20

int maxw=0;	//总重量
int min=65535;	//装的最少数量
int res[MAXN];	//存放最终结果
int count=0;	//测试执行次数 

int getMax(int a,int b)
{//获取两个数中最大值
	return a>b?a:b;
}

void cpsz(int a[],int b[],int n)
{//复制数组
	int i;
	for(i=0;i<n;i++){
		b[i]=a[i];
	}
}

// i 表示层数 
// n 表示总层数
// w[] 存放每个物品重量
// W 表示最大重量
// tw 当前层的重量
// tc 当前层的数量
// opt[] 临时存放解
void f(int i,int n,int w[],int W,int tw,int tc,int opt[])
{//主要递归主体
	count++;
	printf("层数 %d 总层数 %d 最大重量 %d 当前层重量 %d 当前层数量 %d count: %d \n",i,n,W,tw,tc,count);
	if(i<n){
		//取 i 物品 左剪枝
		if(tw+w[i]<=W){
			opt[i]=1;
			f(i+1,n,w,W,tw+w[i],tc+1,opt);
		}
		//右剪枝
		if(tw>3){
			//不取 i 物品
			opt[i]=0;
			f(i+1,n,w,W,tw,tc,opt);
		}
	}else{
		if(tw>=maxw){
			maxw=tw;
			if(tc<min){
				cpsz(opt,res,n);
				min=tc;
			}
		}
	}
}

void printRes(int w[],int n){
	int i;
	printf("问题最终解为\n装箱物品: ");
	for(i=0;i<n;i++){
		if(res[i]==1)
			printf("%d ",w[i]);
	}
	printf("\n装箱最大的容量为:%d",maxw);
	printf("\n装箱数量为%d\n",min);
}

int main()
{
	int n=5;	//n个集装箱
	int W=10;	//轮船最大载重量
	int w[] = {9,4,8,6,1};	//n个物品对应的数量
	int opt[MAXN];	//存放临时解
	f(0,n,w,W,0,0,opt);
	printRes(w,n);
	return 0;
}

进行右剪枝之后程序只执行了 7 次就出答案了, 看来右剪枝的效果还是很明显的, 但是如果题上没有给右剪枝的条件或者要求,最好不要进行右剪枝,以免程序出错。

发布了39 篇原创文章 · 获赞 20 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/cong____cong/article/details/103384237