回溯法求解装载问题(DFS + 剪枝策略)

该博客介绍了如何运用回溯法解决一个特殊的0-1背包问题,即如何在两艘船的重量限制下合理装载n个集装箱。通过建立子集树并利用剪枝策略减少搜索空间,最终找到最优装载方案。算法首先尽可能装满第一艘船,然后尝试将剩余集装箱装入第二艘船。博客提供了详细的算法思路和C++实现代码,并给出了测试样例。
摘要由CSDN通过智能技术生成

参考:https://blog.csdn.net/m0_38015368/article/details/80196634

问题描述:

有n个集装箱要装上2艘载重量分别为c1和c2的轮船,其中集装箱i的重量为wi,且\sum wi \leq c1 + c2

问是否有一个合理的装载方案,可将这n个集装箱装上这2艘轮船。如果有,找出一种装载方案。

问题分析:

如果一个给定装载问题有解,则采用下面的策略可得到最优装载方案。
(1)首先将第一艘轮船尽可能装满;
(2)将剩余的集装箱装上第二艘轮船。

将第一艘轮船尽可能装满等价于选取全体集装箱的一个子集,使该子集中集装箱重量之和最接近c1。由此可知,装载问题等价于以下特殊的0-1背包问题:

max\sum wi * xi      且    \sum wi * xi \leq c1, xi \epsilon \left \{ 0, 1 \right \},1\leq i \leq n

算法思路:

子集树表示解空间,则解为n元向量{x1,  ... ,xn }, xi∈{0, 1} 。

约束函数:

当前搜索的层i <= n时,当前扩展结点Z为子集树的内部结点,对于当前扩展结点,其右子树约束函数值与其父节点相同,则仅当满足cw+w[i] <= c时进入左子树,x[i]=1; 当cw+w[i] > c ,在以结点Z为根的子树中所有结点都不满足约束条件,因而该子树中解都是不可行解,因而将在该子树删去。

限界函数:

由于是最优化问题, 可利用最优解性质进一步剪去不含最优解的子树:
设Z是解空间树第i层上的当前扩展结点。
设           bestw:  当前最优载重量, 
                   cw:  当前扩展结点Z的载重量 ; 
                      r:  剩余集装箱的重量;
在以Z为根的子树中任意叶结点所相应的载重量不超过cw + r。因此,对于当前扩展结点,其左子树限界函数值与其父节点相同,则仅当cw + r  ≤ bestw时,可将Z的右子树剪去。
即:cw +  r > bestw 时搜索右子树,x[i]=0;

#include <iostream>
#include <cstring>

using namespace std;

int n;					//集装箱数 
int bw = 0;				//best_weight:当前最优载重量(已搜索的解空间树中)
int cw = 0;				//current_weight:当前载重量(从根结点到当前结点部分解)
int x[100];				//当前向量 
int bestx[100];			//最优解向量 
int w[100];				//weight:集装箱重量
int c1;					//第一艘船最大载重量
int c2;					//第二艘船最大载重量
int r;					//rest:剩余集装箱重量

void bacctrack(int i)
{
	//搜索到叶子结点,更新最优解 
	if(i > n)
	{
		for(int i = 0; i < n; i++)
			bestx[i] = x[i];
		bw = cw;
	}
	r -= w[i];
	/*
	剪枝 + 搜索
	右儿子的约束函数值与其父节点相同; 
	左儿子的限界函数值与其父节点相同;
	所以在搜索左儿子时,则只需判断约束函数能否将其剪枝 
	搜索右儿子时,则只需判断限界函数能否将其剪枝 
	*/ 
	//搜索左儿子 
	if(cw + w[i] <= c1) 
	{
		x[i] = 1;
		cw += w[i];
		bacctrack(i + 1);
		cw -= w[i];		//回溯到父节点时,cw要更新为: cw - w[i]
	}
	//搜索右儿子 
	if(cw + r > bw)
	{
		x[i] = 0;
		bacctrack(i + 1);					//回溯到父节点时,r要更新为:r + w[i]	
		r += w[i];						
	}
} 

int main()
{
	cout << "请输入集装箱数量:" << endl; 
	cin >> n;
	cout << "请输入两艘船最大载重量:" << endl;
	cin >> c1 >> c2; 
	cout << "请输入集装箱重量:" << endl; 
	for(int i = 1; i <= n; i++)
		cin >> w[i];
	//将r初始化为所有集装箱的重量之和,那么当前扩展结点的r = r - w[i],避免每次计算的前结点的cw + r 
	for(int i = 1; i <= n; i++)
		r += w[i];
	bacctrack(1);
	//判断在第一艘船尽可能装满后,剩下的集装箱能否装入第二搜船 
	int rest_sum = 0;  
	for(int i = 1; i <= n; i++)
	{
		if(bestx[i] == 0)
		{
			rest_sum += w[i];
		}	
	}
	if(rest_sum > c2)
		cout << "无法装入" << endl;
	else
	{
		cout << "第一艘船装入的货物是:";
		for(int i = 1; i <= n; i++)
		{
			if(bestx[i] == 1)
				cout << i << " ";
		}
		cout << endl;
		cout << "第二艘船装入的货物是:";
		for(int i = 1; i <= n; i++)
		{
			if(bestx[i] == 0)
				cout << i << " ";
		}
		cout << endl;
	}
	return 0;
}	

测试样例:

(1)

 

(2)

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值