问题描述:
一共有n个货物要装上两艘重量分别为c1和c2的轮船上,其中货物i的重量为Wi,且:
要求确定是否有一个合理的装载方案可将货物装上这两艘轮船。
采取策略:
(1)首先将第一艘轮船尽可能装满;
(2)将剩余的集装箱装上第二艘轮船。将第一艘轮船尽可能装满等价于选取全体集装箱的一个子集,
使该子集中集装箱重量之和最接近。由此可知,装载问题等价于以下特殊的0-1背包问题:
限界函数:
定义一个CW表示到达当前结点O的重量,如果CW>C1,表示以O为根的子树不能产生一个可行的解,避免移动。
缺陷:
一个可行结点的右孩子总是可行。
算法改进:
如果当前结点的右子树不可能包含比当前最优解更好的解,就不移动到右子树上。
设bestW为当前的最优解,Z为解空间树的第i层的一个结点。
改进的限界函数:
设r为剩余货箱的重量,当CW+r<=bestW时,没有必要去搜索Z的右子树。
话不多说,直接上代码:
package set; public class Loading { static int n;//货箱数目 static int[] w;//货箱重量数组 static int c;//第一艘船的重量 static int cw;//当前装载的重量 static int bestw;//目前最优装载的重量 static int r;//剩余货箱的重量 static int[] x;//当前解,记录从根至当前结点的路径 static int[] bestx;//记录当前最优解 public static int MaxLoading(int[] ww,int cc) { //初始化数据成员,数组下标从1开始 n = ww.length - 1; w = ww; c = cc; cw = 0; bestw = 0; x = new int[n+1]; bestx = new int[n+1]; //初始化r,即剩余最大重量 for(int i =1;i<=n;i++) { r += w[i]; } //计算最优载重量 backtrack(1); return bestw; } //回溯算法 public static void backtrack(int t) { if(t>n) {//到达叶结点 if(cw>bestw) { for(int i=1;i<=n;i++) { bestx[i] = x[i]; } bestw = cw; } return; } r -= w[t]; if(cw + w[t] <= c) {//搜索左子树 x[t] = 1; cw += w[t]; backtrack(t+1); cw -= w[t];//回溯 } if(cw + r>bestw) { x[t] = 0;//搜索右子树 backtrack(t+1); } r += w[t];//恢复现场 } /* * 如果当前节点的右子树不可能包含比当前最优解更好的解时,就不移动到右子树上! 设bestw为当前最优解,Z为解空间树的第i 层的一个节点 为剩余货箱的重量;当cw+r<=bestw时,没有必要去搜索Z 的右子树: 当前载重量cw+剩余集装箱的重量r当前最优载重量bestw */ public static void main(String[] args) { // TODO Auto-generated method stub int[] ww = {0,20,30,60,40,40}; int c1 = 100; int c2 = 100; int n = ww.length - 1; MaxLoading(ww,c1); int weight2 = 0;//保存第二艘船可能要装的重量 for(int i=1;i<=n;i++) { weight2 += ww[i]*(1-bestx[i]);//bestx[i]的值只能为0或1 } if(weight2>c2) { System.out.println("无解"); } else { System.out.println("第一艘船装载货物的重量: " + bestw); System.out.println("第二艘船装载货物的重量: " + weight2); //第一艘船的装载情况 for(int i = 1;i<=n;i++) { if(bestx[i] == 1) { System.out.println("第" + i + "件货物装入第一艘船"); } } //第二艘船的装载情况 for(int i = 1;i<=n;i++) { if(bestx[i] == 0) { System.out.println("第" + i + "件货物装入第二艘船"); } } } } }
运行结果: