上机实验题7——求解装载问题
有 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个集装箱。
分析
假想存在一颗二叉树,每一层的结点对应已考虑 i 个集装箱后选取的集装箱的总重量。每一条边的值为 0 或 1 对应是否选择第 i 个集装箱。
根节点值为 0 ,代表最初还没有开始选择集装箱的状态。
对于第 i 个要装入的集装箱分两种情况。如果将其装入,相当于走了左子树,此时左孩子的结点值为父结点值加上第 i 个集装箱的重量。如果不将其装入,相当于走了右子树,此时右孩子的结点值和父节点值相等。
上面相当于是假想了一个完全二叉树,接下来要进行剪枝。
左剪枝是对于左孩子结点的剪枝,当装载的集中箱总重量大于 W ,说明找到了一个可行解(不一定是最优解),所以不需要再走左子树,要走右子树。
右剪枝是对于右孩子结点的剪枝。如果装载的集中箱数量加下剩余的集装箱数量小于 3 ,说明此次选择不可能选够 3 个集装箱,所以不再进行右子树的搜索。
如果i>n说明已经走完了叶子结点即已经考虑了 i 个集装箱,这是一个可行解。如果集装箱的总重量大于最优解的集装箱总重量或者集装箱的总重量等于最优集装箱总重量且比最优集装箱数量少,说明它可能是新的最优解,需要将其赋给最优解。
代码
# include<stdio.h>
# define MAXN 20 //最多集装箱个数
int n, W;
int maxw; //存放最优解的总重量
int x[MAXN]; //存放最优解向量
int minm = 32767; //存放最优解的集装箱个数,初值为最大值
void Loading(int w[], int tw, int m, int r, int op[], int i)//考虑第i个集装箱
{
//tw为考虑第i个集装箱时装入的总重量,其装载解向量为op
int j;
if(i > n) //找到一个叶子节点
{ if(tw <= W&&(tw > maxw||(tw==maxw && m<minm)) && m>=3)
//找到一个满足条件的更优解,保存它
{
maxw = tw;
minm = m;
for(j = 1 ;j <= n; j++)
x[j] = op[j];
}
}
else
{ //尚未找完所有物品
op[i] = 1; //选取第i个集装箱
if(tw+w[i] <= W)//左孩子结点剪枝:装载满足条件的集装箱
{
Loading(w, tw+w[i], m+1, r-1, op, i+1);
}
op[i] = 0; //不选取第i个集装箱,回溯
if(r+m >= 3)//右孩子结点剪枝 :至少要选3个集装箱
Loading(w, tw, m, r, op, i+1);
}
}
void dispasolution(int n) //输出一个解
{
int i;
printf("选取的集装箱:\n");
for(i = 1; i <= n; i++)
if(x[i] == 1)
printf("选取第%d个集装箱\n", i);
printf("总重量=%d\n", maxw);
}
int main()
{
int w[] = {0,20,3,5,2,6,4,3,9}; //各集装箱重量,不用下标0的元素
int op[MAXN]; //存放临时解
n = 8, W = 20; //n是集装箱的个数,W是集装箱总重量,
int r = n; //r是集装箱剩余数量初始值为n
Loading(w, 0, 0, n, op, 1);
dispasolution(n);
return 0;
}