一、题意描述:
有n个集装箱要装上2艘载重量分别为C1和C1的轮船。其中集装箱i的重量为Wi,且(W1+W2+….+Wn<=C1+C2)
装载问题是,是否有一个合理装载方案,可将这n个集装箱都装上这2个轮船,若有,请给出解决方案。二、分析:
刚看到这道题,觉得一定有解,认真想想就会发现不一定。
例如:
C1=C2=50, W=(10,40,40) 可以装载(10,40)、(40)
C1=C2=55, W=(20,40,40) 无法装载
若一个给定的装载问题有解的话,可以证明,以下策略可以得到最优装载方案:
(1)首先将第一艘轮船尽最大可能装满;
(2)然后将剩余的集装箱装上第二艘轮船。
三、回溯法源代码:
//最优装载问题,时间复杂度为O(2^n)
#include<iostream>
#include<cstring>
using namespace std;
#define N 10
int n;//n为集装箱的个数
int c1;//船1的装载容量
int c2;//船2的装载容量
int x[N];//存哪些节点存进树中了1,哪些节点没有存进去 0
int w[N];//每个集装箱的重量
int bestx[N];
int cw;//现在已经计算出来的集装箱的和
int bestcw=0;//最优解
void backtrack(int t,int c){
if(t>n){
if(cw>bestcw){
bestcw=cw;
for(int i=1;i<=n;i++){
bestx[i]=x[i];
}
}
else return;
}else{
if(cw+w[t]<=c){//搜索左子树
cw+=w[t];
x[t]=1;
backtrack(t+1,c);
cw-=w[t]; //还原
x[t]=0;//返回上一层是两个都还原
}else{
x[t]=0;
backtrack(t+1,c);//搜索右子树
}
}
}
int main(){
memset(x,0,sizeof(x));
memset(bestx,0,sizeof(bestx));
cout<<"请输入集装箱的个数:"<<endl;
cin>>n;
cout<<"请输入第一艘船的最大载重量:"<<endl;
cin>>c1;
cout<<"请输入第二艘船的最大载重量:"<<endl;
cin>>c2;
cout<<"请输入每个集装箱的重量:"<<endl;
for(int i=1;i<=n;i++)
cin>>w[i];
cout<<"第一艘船上装的集装箱是:"<<endl;
backtrack(1,c1);
for(int i=1;i<=n;i++)
if(bestx[i])
cout<<i<<" ";
cout<<endl;
cout<<"第一艘船的最优载重量是:";
cout<<bestcw<<endl;
bestcw=0;
for(int i=1;i<=n;i++){
if(!bestx[i])
bestcw+=w[i];
}
if(bestcw>c2){
cout<<"这艘船不能装下剩下的所有集装箱!"<<endl;
}else{
cout<<"第二艘船上装的集装箱是:";
for(int i=1;i<=n;i++)
if(!bestx[i])
cout<<i<<" ";
cout<<endl;
cout<<"第二艘船的最优载重量是:";
cout<<bestcw<<endl;
}
return 0;
}
四、优化之后:(在这里我只写回溯法的函数了,其他的和上面一样)
int r;//r为剩余未判断的物品重量
void backtrack(int t,int r){
if(t>n){
if(cw>bestcw){
bestcw=cw;
for(int i=1;i<=n;i++){
bestx[i]=x[i];
}
}
else return;
}else{
r-=w[t];
if(cw+w[t]<=c1){
cw+=w[t];
x[t]=1;
backtrack(t+1,r);
cw-=w[t];
}
if(cw+r>bestcw){//搜索右子树,当当前的和加上未遍历的节点大于最优解时才搜索右子树
x[t]=0;
backtrack(t+1,r);
}
r+=w[t]; //!!!返回上层前,还要还原剩余载重量和。
}
}
int main(){
(其他省略,与上一个源代码一样)
//计算r
for(int i=1;i<=n;i++){
r+=w[i];
} .......