上一次介绍了集装箱装载问题的队列式分支限界法,本次将分享优先队列式分支限界法解决这个问题的算法。
Problem:
装载问题的问题提出是,有一批共n个集装箱要装上2艘载重量分别为 c1和c2 的轮船,其中集装箱i的重量为 wi,且(集装箱总重量和)w<=c1+c2 。问是否有一个合理的装载方案能将这n个集装箱装上这两艘轮船。
由于 王晓东先生著的《算法设计与分析》里面的代码由于缺少了最大堆类的描述,所以并不能运行,这里对该算法进行了增加与修改。若有需要的同学可以参考一下。
//优先队列式分支限界法_集装箱装载问题
public class PriorityBBloding {
// 优先队列式分支限界法,返回最优载重量 bestx返回最优解
static MaxHeap heap=new MaxHeap();
static int n;//集装箱数量
static class BBNode//解空间树的结点类
{
BBNode parent; //指向父结点的指针
boolean leftChild; //左儿子结点标志
BBNode(BBNode par,boolean ch)
{ parent=par;
leftChild=ch;
}
}
static class HeapNode implements Comparable//堆的元素类型类
{ BBNode liveNode; //指向活结点在子集树中相应结点的指针
int uweight; //活结点优先级(上界)
int level; //活结点在子集树中所处的层序号
HeapNode(BBNode node,int up,int lev)
{//构造方法
liveNode=node;
uweight=up;
level=lev; }
public int compareTo(Object x)
{
int xuw=((HeapNode)x).uweight;
if(uweight<xuw)return -1;
if(uweight==xuw)return 0;
return 1;
}
public boolean equals(Object x)
{
return uweight==((HeapNode)x).uweight;
}
}
//从该方法可以看出该解法只是构建部分解空间树
private static void addLiveNode(int up,int lev,BBNode par,boolean ch)//将一个新产生的BBNode类型活结点加入到子集树中,并将这个新结点插入到表示活结点优先队列的最大堆中
{//将一个新产生的活结点插入到表示活结点优先队列的最大堆中
BBNode b=new BBNode(par,ch);
HeapNode node=new HeapNode(b,up,lev);
heap.insert(node);
}
//用于存放活结点的最大堆类
static class MaxHeap{
private static HeapNode[] maxheap;//最大堆的存储空间
private static int front;//堆顶引用,若堆不空,指向堆顶元素
private static int rear;//堆尾引用,若堆不空,指向堆尾元素的下一个位置
public MaxHeap() {//初始化最大堆
front=rear=0;
maxheap=new HeapNode[100];
}
//筛选法调整堆
//将以low为根结点的子树调整成大顶堆,low和high分别是待调整序列的上界和下界
static void sift(int low,int high) {
int i=low;//子树的根结点
int j=2*i+1;//j为i结点的左孩子
HeapNode temp=maxheap[i];
while(j<high) {//沿较大值的孩子结点向下筛选
if(j<high-1&&maxheap[j].uweight<maxheap[j+1].uweight) {
j++;//结点优先级进行比较,j为左右孩子结点的较大者
}
if(temp.uweight<maxheap[j].uweight) {//若父母结点值较小
maxheap[i]=maxheap[j];//孩子结点的较大者上移
i=j;
j=2*j+1;//对以被交换的子结点作为根结点所在的子树进行调整
}else {
j=high+1;//退出循环
}
}
maxheap[i]=temp;//当前子树的原根值调整后的位置
}
//创建堆算法
static void insertheapSort() {
int n=rear-front;//待加入堆的结点个数
HeapNode temp;
for(int i=n/2-1;i>=front;i--) {//创建堆
sift(i,n);
}
}
//取出堆顶元素,并且重新调整堆为最大堆的算法
static HeapNode removeheapSort() {
int n=rear-front;//
int i=n-1;//堆的最后一个结点
HeapNode temp=maxheap[front];//将堆中最小关键字值移到最前面
maxheap[front]=maxheap[i];
sift(front,i);//并且调整成堆
return temp;//返回最顶堆结点
}
//将堆元素node加入堆,并且调整堆
static void insert(HeapNode node) {
maxheap[rear]=node;
rear+=1;//修改尾指针
insertheapSort();//调整堆
}
//将堆顶元素取出,并且调整堆
public HeapNode removeMax() {
HeapNode temp=removeheapSort();
rear-=1;
return temp;
}
}
public static int maxLoading(int[] w,int c,int []bestx)
{
// 初始化
BBNode A=null;
BBNode e= A; //当前扩展结点
int i=1; //当前扩展结点所处的层
int ew=0; //扩展结点所相应的载重量
int [ ]r=new int[n+1]; //定义剩余重量数组r
for(int j=n-1;j>=0;j--)
{r[j]=r[j+1]+w[j+1];}
//搜索子集空间树
while(i!=n+1)
{//非叶结点,检查当前扩展结点的儿子结点
if(ew+w[i]<=c)
//左儿子结点为可行结点
addLiveNode(ew+w[i]+r[i],i+1,e,true);
// 右儿子结点总为可行结点
addLiveNode(ew+r[i],i+1,e,false);
//取下一个扩展结点
HeapNode node=(HeapNode)heap.removeMax();
i=node.level;
e=node.liveNode; //下一扩展结点
ew=node.uweight-r[i-1];
}
for(int j=n;j>0;j--)
{//构造当前最优解
bestx[j]=(e.leftChild)?1:0;
e=e.parent;
}
return ew;
}
public static void main(String[] args) {
n=5;//集装箱个数
int []w= {0,60,40,10,30,50};//集装箱重量
int c=120;//轮船载重量
int[] bestx=new int[n+1];//最优解
int a=maxLoading(w,c,bestx);
System.out.println("最优载重量为:"+a);
System.out.println("最优解为:");
for(int i=1;i<n+1;i++) {
System.out.print(bestx[i]+" ");
}
}
}