一、问题描述
有n个集装箱要装上2艘载重量分别为c1和c2的轮船,其中第i个集装箱i的重量为w[i],要求确定是否有一个合理的装载方案可将这些集装箱装上这2艘轮船。如果有,找出一种装载方案。
二、问题分析
(1) n个集装箱的重量之和肯定小于等于2艘轮船的载重量c1+c2,不然不可能全部装入,即∑w[i] <= c1 + c2。
(2) 为将所有集装箱装入2艘轮船,我们无需考虑如何将集装箱分配到2艘轮船,只需考虑如何将1艘轮船尽可能装满。第1艘轮船能最大程度装满,第2艘轮船能装下就是能装下,装不下就是装不下。
三、算法思路
(1) 何为解空间树?以该问题为例,每个集装箱可以看成一个结点,而每一个集装箱有装或不装两种选择。因此,每个结点会出现两条分支,利用深度优先搜索,最后会形成一棵完全二叉树,这就是解空间树。
(2) 何为回溯法?回溯法,利用深度优先搜索策略,从根节点出发搜索解空间树。搜索至任一结点时,判断该节点是否包含问题的解。如果不包含,跳过即可;如果包含,进入该子树,继续按照深度优先搜索策略搜索。
(3) 目标函数:在解空间树中找到一条路径,使所有集装箱可以装入2艘轮船。
(4) 约束条件:装入第1艘轮船的重量之和需要小于等于第1艘轮船的载重量,即∑x[i]w[i] <= c1。
(5) 限界条件:如果要搜索整棵二叉树,会消耗许多时间。因此,利用限界函数剪去不包含最优解的子树,减少不必要的搜索,加快搜索进度。在该问题中,假设cw是已装入的重量,rw是未装入的重量,bestW是当前的最优值(装入的重量和最大)。当搜索至任一结点时,如果不选择当前结点,形成一条分支,则该分支必然可行。但是,该分支不一定包含最优解。此时,若cw+rw <bestW,即小于最优值,明显不需要搜索该路径。因此,剪去该路径,减少搜索时间。
四、源代码
import java.util.Scanner;
public class MaxLoading {
static final int NUM = 100;
// 集装箱重量
static int[] w=new int[NUM];
// 最优解
static int[] bestX = new int[NUM];
// 当前解
static int[] x= new int[NUM];
// 最优装船重量
static int bestW = 0;
// 物品个数
static int n;
// 第一个船的载重量
static int c1;
// 第二个船的载重量
static int c2;
// 当前已装船重量
static int cw = 0;
private static int bound(int t) {
// 初始化剩余重量
int rw = 0;
for (int i = t+1;i <= n;++i) {
// 计算剩余集装箱重量
rw += w[i];
}
// 返回可装的总重量
return rw+cw;
}
/**
* 回溯法
* @param t :第几个集装箱
*/
public static void loadingRec(int t) {
if (t > n) { // 集装箱装箱完毕
if (cw > bestW) { // 如果当前重量大于最优重量
// 更新最优重量
bestW = cw;
for (int i = 1; i <= n; i++) {
// 最优解更新为当前解
bestX[i] = x[i];
}
}
return;
}else { // 尚未结束装箱
if (cw + w[t] <= c1) { // 若装入当前集装箱,且重量小于船载重量
// 加上此集装箱重量
cw +=w[t];
// 选择该集装箱
x[t] = 1;
// 下一个
loadingRec(t+1);
cw -= w[t];
x[t] = 0;
}
if (bound(t) > bestW) { // 不装第t个集装箱,若总重量大于最优重量
loadingRec(t+1);
}
}
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
System.out.print("请输入集装箱的个数:");
n = in.nextInt();
System.out.println("请输入集装箱的重量(整数):");
for (int i = 1; i <= n;++i) {
w[i] = in.nextInt();
}
in.nextLine();
System.out.print("请输入第一船的载重量:");
c1 = in.nextInt();
in.nextLine();
System.out.print("请输入第二船的载重量:");
c2 = in.nextInt();
loadingRec(1);
System.out.println("第一船的最优重量为:" + bestW);
System.out.println("第一船的最优解为:");
for (int i = 1; i <= n;++i) {
if (bestX[i] == 1) { // bestX[i] == 1,表示选中
System.out.println("物品" + i + "装入第1个集装箱");
}
}
int cw2 = 0;
for (int i = 1;i <= n;++i) {
// 计算剩余重量
if (bestX[i] == 0) {
cw2 += w[i];
}
}
// 剩余重量小于第二个船的载重量,可以装入
// 下行为小于第二艘船的载重量
if (cw2 <= c2) {
System.out.println("可以将剩余集装箱装入第二船,问题有解");
}else { // 剩余重量大于第二个船的重量,不能装入
System.out.println("不能将剩余集装箱装入第二船,问题无解");
}
in.close();
}
}