问题
要求确定一个合理的装载方案将n个集装箱装入两艘船,如果有,找出一种方案
分析
我们之前用贪心做过一艘船的最优装载问题(将尽可能多的集装箱装入),即将重量小的集装箱先装。
那么我们看看这个题,如果用贪心的话能不能做出来。
假设两艘船载重量为100 100,
假设有5个物体,重量为20 30 40 50 60,如果我们用贪心算法,那么第一艘船装入的物品为 20 30 40,共三个
那么另一艘船只能放入50,共一个
此时我们发现贪心算法明显出了问题
因为可以第一艘船放入:20 30 50
第二艘船放入40 60
此时刚好完全装入
所以我们需要另一种方法:
将第一艘船装入尽可能重的集装箱
当剩余集装箱重量小于第二艘船时,那么能够装下所有集装箱。
如何使第一艘船装入尽可能多的集装箱呢?
这有点类似于0-1背包问题
但我们会使用回溯法,因为某些情况下回溯法优于动态规划
此时解空间为子集树
当所在状态的重量加上所在点重量小于载重量时(越界判断),我们就可以进入左子树进行搜索尝试。
当所在状态的重量加上所有剩余重量大于当前最优值时**(剪枝)**,我们就可以进入右子树进行判断。
代码实现
#include<stdio.h>
#define N 100
int c1,c2;//装载量
int n; //物品数目
int w[N];//物品重量
int flag[N]={0};//标记
int r=0;//总余量
int cw=0;//当前重量
int bw=0;//最优解
int choose[N];//保存数组
void backtrack(int i){
int j;
if(i>n){//到达叶节点
if(cw>bw){//更新最优值
bw=cw;
for(j=1;j<=n;j++){
choose[j]=flag[j];
}
}
return ;
}
r-=w[i];//更新剩余容量
if(cw+w[i]<=c1){//左子树(加上此节点重量不超过c1)
cw+=w[i];//更新现有重量
flag[i]=1;//标记,即记录数据
backtrack(i+1);//递归
cw-=w[i];//恢复
flag[i]=0;
}
if(cw+r>bw){//右子树,如果现有重量加上剩余量不超过最优值时剪枝
backtrack(i+1);//递归
}
r+=w[i];//恢复余量
}
int main()
{
int i,sum=0;
printf("请输入c1,c2装载量和物品数目:");
scanf("%d %d %d",&c1,&c2,&n);
for(i=1;i<=n;i++){
scanf("%d",&w[i]);
r+=w[i];
sum+=w[i];
}
backtrack(1);
if(sum-bw<=c2){
printf("能够将物品装下\nc1放的物品为:");
for(i=1;i<=n;i++){
if(choose[i]==1) printf("%d ",i);
}
printf("\nc2放的物品为:");
for(i=1;i<=n;i++){
if(choose[i]==0) printf("%d ",i);
}
} else{
printf("不能放入所有物品\n");
}
}