问题描述:
有一批共有 n 个集装箱要装上两艘载重量分别为 c1 和 c2 的轮船,其中集装箱 i 的重量为 w[i], 且重量之和小于(c1 + c2)。装载问题要求确定是否存在一个合理的装载方案可将这 n 个集装箱装上这两艘轮船。如果有,找出一种装载方案。
例如,当n=3,c1=c2=50,且w=[10,40,40]时,可将集装箱1和集装箱2装上一艘轮船,而将集装箱3装在第二艘轮船;如果w=[20,40,40],则无法将这3个集装箱都装上轮船。
容易证明,如果一个给定的装载问题有解,则采用如下的策略可以得到最优装载方案。
1.首先将第一艘轮船尽可能装满。
2.将剩余的集装箱装上第二艘轮船。
将第一艘轮船尽可能的装满等价于选取全体集装箱的子集,使该子集中集装箱的重量之和最接近 c1 。因此,等价于一个特殊的 0-1 背包问题。 因此是一棵子集树。
max(w1x1+w2x2+…+wixi)
(w1x1+w2x2+…+wixi)<= c1;
xi @{0,1},1<=i<=n
当然可以用第三章中讨论过的动态规划算反解这个特殊的0-1背包问题。所需的计算时间是O(min{c1,2n})。下面讨论用回溯法设计解装载问题O(2n)计算时间算法。在某些情况下该算法优于动态规划算法。
注:该问题只能应用于有且只有两艘轮船的情况。
package n20_装载问题回溯算法;
/* * 用回溯算法解装载问题 */
public class Main {
static int[]w={ 0, 5, 30, 10, 1, 20, 40, 20}; // 4个集装箱,w[0]放弃
static int n = w.length - 1;//集装箱的个数
static int bestw = 0;//当前的最优值
static int cw = 0;//当前轮船的装载量
static int c =58;//轮船最大的核载量
static int r = 0;//剩余集装箱的重量
static int []bestx=new int [n+1];//用来存放当前的最优解
static int x[]=new int[n+1];//当前解
public static void main(String[] args) {
for (int i = 1; i <= n; i++) {//刚开始时,剩余的集装箱重量是所有集装箱的重量
r += w[i];
}
int tempR=r;//临时变量,求最优解时要用到
f(1);//从第一个集装箱k
System.out.println(bestw);//输出最优值
//以下各步骤是求最优解的
cw = 0;//重置当前轮船的装载量
r = tempR;//重置剩余集装箱的重量
f2(1); //f2是用来求最优解的,和f稍有不同
for(int i=1;i<=n;i++){ //输出最优解
System.out.print(bestx[i]+" ");
}
}
private static void f2(int i) {
//搜索第i层结点
if (i > n) { //到达叶结点
for(int j=1;j<=n;j++){
bestx[j]=x[j];
}
bestw = cw;
return;
}
//搜索子树
r -= w[i];
if (cw + w[i] <= c) { //搜索左子树
x[i]=1;
cw += w[i];
f2(i + 1);
cw -= w[i];
}
if (cw + r >= bestw) { //搜索右子树(特别注意,这里是>=号,而在f()方法的这里可以不需要).
x[i]=0;
f2(i + 1);
}
r += w[i];
}
private static void f(int i) {
//搜索第i层结点
if (i > n) { //到达叶结点
bestw = cw;
return;
}
//搜索子树
r -= w[i];
if (cw + w[i] <= c) { //搜索左子树
cw += w[i];
f(i + 1);
cw -= w[i];
}
if (cw + r > bestw) { //搜索右子树
f(i + 1);
}
r += w[i];
}
}