问题描述 : 有一批共 n n n个集装箱要装上 2 2 2艘载重量分别为 c 1 c1 c1和 c 2 c2 c2的轮船,其中集装箱 i i i的重量为 w i wi wi,且 ∑ i = 1 n ⩽ c 1 + c 2 \sum_{i = 1}^{n}\leqslant{c1 + c2} ∑i=1n⩽c1+c2,装载问题要求确定是否有一个合理的装载方案可将这个集装箱装上这2艘轮船。如果有,找出一种装载方案。
思路
容易证明,如果一个给定装载问题有解,则采用下面的策略可得到最优装载方案。
- 首先将第一艘轮船尽可能装满
- 将剩余的集装箱装上第二艘轮船。将第一艘轮船尽可能装满等价于选取全体集装箱的一个子集,使该子集中集装箱重量之和最接近c1。由此可知,装载问题等价于以下特殊的0-1背包问题。
用回溯法解装载问题时,用子集树表示其解空间显然是最合适的。用可行性约束函数可剪去不满足约束条件
∑
i
=
1
n
w
i
x
i
⩽
c
1
\sum_{i = 1}^{n}w_ix_i\leqslant c1
∑i=1nwixi⩽c1的子树。在子集树的第
j
+
1
j+1
j+1层的结点z处,用
c
w
cw
cw记当前的装载重量,即cw=
∑
i
=
1
n
w
i
x
i
\sum_{i = 1}^{n} w_ix_i
∑i=1nwixi,则当
c
w
>
c
1
cw > c1
cw>c1时,以结点z为根的子树中所有结点都不满足约束条件,因而该子树中的解均为不可行解,故可将该子树剪去。
详细代码如下
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <cstdio>
using namespace std;
// 集装箱的总数
int n = 0;
// 第一艘船和第二艘船分别可以装的最大重量
int first, second;
// 第一个集装箱能装下的最大重量
int best = 0;
// 总重量
int sum = 0;
// 数组,每一个位置存储集装箱的重量
int *a = NULL;
/**
* @param flag 这个结点是否装上船
* @param times 到数组的哪一个下标了
* @param weight 当前装载的重量
*/
void TraceBack(bool flag, int times, int weight)
{
// 到叶子结点了,直接返回(也是到了数组的边界)
if (times >= n)
{
return;
}
// 当前结点不取
if (flag == false)
{
// 重量不发生变化,向下一层递归搜索
TraceBack(true, times + 1, weight);
TraceBack(false, times + 1, weight);
return;
}
// 装上当前结点
int now = weight + a[times];
// 如果大于第一艘船所能装载的最大重量
if (now > first)
{
return;
}
// 当前能装下的重量大于最优值时,更新最优值
if (now > best)
{
best = now;
}
//下一层还是分为选或者不选
TraceBack(true, times + 1, now);
TraceBack(false, times + 1, now);
}
int main()
{
printf("请输入第一艘船和第二艘船分别最大可以装载的货物重量 : \n");
scanf("%d%d", &first, &second);
printf("请输入货物的数量 : \n");
scanf("%d", &n);
a = new int[n];
for (int i = 0; i < n; i++)
{
scanf("%d", &a[i]);
sum += a[i];
}
// 这里由于是从第一个结点开始的,有选和不选两种情况
TraceBack(true, 0, 0);
TraceBack(false, 0, 0);
// 剩下未装载的容量
int least = sum - best;
// 未装载的容量大于第二艘船能装下的最大容量
if (least > second)
{
cout << "船装不下" << endl;
}
else
{
cout << "第一艘船装载的重量为 : " << best << endl;
cout << "第二艘船装载的重量为 : " << least << endl;
}
delete[] a;
return 0;
}
/*
50 50
3
10 40 40
*/