装载问题
问题:假设有两只船,各可以载重c1,c2。同时有n个集装箱,每个集装箱重量为w[i],问能否将集装箱放入船内,能放入则就最优载重。
分析:
其实和背包问题如出一辙。如果一个给定的装载问题有解,则我们采用的策略应该是:先将第一艘轮船尽可能装满,然后将剩余的集装箱上第二艘轮船(如果不能把所有物品装入第二艘船那么问题无解)。将第一艘轮船尽可能装满等价于选取全体集装箱的一个子集,使该子集中集装箱重量之和最接近c1(即求出最解)。由此可知,装载问题等价于特殊的0-1背包问题。
递归算法解决。没有到达叶结点,先考虑是否满足约束条件,若满足就加上该物品(进入左子树),将解加到解数组里面,更新cw即cw+=w[t],再判断下一步(t+1)是否能左走,能则一直向左走,直到遇到叶结点或者cw+w[i]>c1则不能向左走。遇到叶子结点则更新bestw,bestx、不能向左走就只能判断能向右走。然后就要考虑是否需要进入右子树,那就要判断当前的结点是否满足上界函数即cw+r>bestw:满足则进入右子树,回溯到右子树必须得先回到父节点,那么之前在左边加上的质量就要减掉即cw-w[t],然后才可以进入右子树即执行(t+1)步。不满足的话即便把所有的剩余质量都加到船上也不能达到更大的bestw,所以直接舍弃剩余右子树,不再进行搜索。
源代码:
#include<iostream>
using namespace std;
#define NUM 100
int n; //集装箱数量
int c1,c2; //轮船载重量
int cw; //当前轮船载重量
int r; //剩余集装箱重量
int w[100],bestw,x[100],bestx[100]; //最优载重,最优解
//t从1开始
void BackTrack(int t)
{
//到达叶子节点
if(t>n){
cout << cw << endl;
if(cw > bestw){
for(int i = 1; i <= n; i++){
if(x[i])
bestx[i] = i;
else
bestx[i]=x[i];
}
bestw = cw;
return;
}
}else{
r -= w[t]; //更新剩余集装箱重量
if(cw + w[t] <= c1){ //没有超出载重量,判断是否可以向左
x[t] = 1;
cw += w[t];
cout << "左:" << t << " " << bestw << endl;
BackTrack(t+1);
cw -= w[t]; //cw要还原原先的载重,因为还要判断其他路线才能找到最优解
}
if(cw + r > bestw){ //判断是否可以向右
cout << "右:" << t << " " << bestw << endl;
x[t] = 0;
BackTrack(t+1);
}
r += w[t]; //还原剩余集装箱重量走其他路线
}
}
int main(){
//读入数据;
cout << "请输入AB船的容量c1、c2:";
cin >> c1 >> c2;
cout << "输入物品的数量:";
cin >> n;
cout << "依次输入物品的质量:";
for(int i = 1; i <= n; i++){//初始化,bestw=0
cin >> w[i];
r += w[i];
}
//递归回溯
BackTrack(1);
if(r-bestw > c2)//无解
cout << "没有装载方案" << endl;
else{//有解
cout << "最有载重为:" << bestw << endl;
cout << "分别为第" ;
for(int i = 1; i <= n; i++)
if(bestx[i])
cout << bestx[i] << " ";
cout << "个箱子" << endl;
}
return 0;
}
下面为给的例子和结果
我把进入左右子树的过程都打了出来,可以很清晰的看到每一个箱子进入cw的过程,已经最优解得到的过程