装载问题——优先队列
一、装载问题
1、问题描述
有两艘船和需要装运的n个货物,第一艘船的载重量是c1,第二艘船的载重量是c2,wi是货物i的重量,且w1+w2+w3+…+wn<=c1+c2。希望确定是否有一种可将所有n个货物全部装船的方法。若有,请找出该方法。
2、问题分析
先来根据一个实例分析一下问题。当n=3,c1=c2=50,w={10,40,40}时,可将货物1,2装到第一艘船上,货物3装到第二艘船上。即可装完这些货物,也就完成了该问题。但是如果w={20,40,40}时,则无法将所有货物全部装到船上。由此可知,该问题可能有解、可能无解,也可能有多解。
虽然是两艘船的装载,其实只讨论一艘船的最大装载问题即可。因为当第一艘船达到最大装载后,剩余的货物只能装在第二艘船上,如果装不下,则无法完成该装载过程。
其实好多地方说的装载问题,都只是说的两艘船的装载。对于每艘船的装载来说,其装载货物的
过程都是相似的。每次不同船的装载,只需改一下参数,也就是货船载重量、货物数量与重量,
即可。基于此思想,本博客将实现多艘船的装载。
二、优先队列
1、基本介绍
优先队列,是计算机科学中的一类抽象数据类型,属于“容器数据类型”。优先队列中的每个元素都有自己的优先级,优先级高的先得到服务,优先级相同的元素按照其在优先队列中的顺序得到服务。其实这里所讲的优先队列和我们经常所说的队列本质上是一样的,毕竟,它们都是“队列”。只不过,普通的队列是以进入队列的顺序为优先级的,也就是我们所说的“先进先出(FIFO)”。优先队列往往用堆排序来实现。
2、标准与实现
优先队列的标准是由标准模板库STL(Standard Template Library)规定的,其实现模板为priority_queue<type,container,function>。其中,type为数据类型,container是容器类型(STL默认的是vector),function是比较方式。当需要自定义数据类型时,需要把这三个参数同时传入。使用基本的数据类型时,只需传入数据类型,默认是大顶堆,降序序列,比较函数是less,小顶堆的升序序列,比较函数是greater。
3、常用操作
优先队列具有队列的所有基本特性,包括基本操作,只是在这基础上加上了内部自动排序,它的本质是靠堆实现的。优先队列的最大的特性就是自动排序。
//以一个名为q的优先队列为例
q.size() //返回q里的元素个数
q.empty() //返回q是否为空,空则返回1,反之返回0
q.push(k) //在q的末尾插入k
q.pop() //删除q的队头元素
q.top() //返回q的队头元素
三、算法思想
1、算法描述——分支限界法
使用优先队列解决装载问题,其实,本质上是分支限界法的使用。分支限界法是由“分支”和“限界”策略两部分组成。“分支”策略体现在对问题空间是按广度优先的策略进行搜索;“限界”策略是为了加速搜索速度而采用启发信息剪枝的策略。
2、分支限界法之优先队列
(1)优先队列搜索
优先队列(Priority Queue)式搜索,是对一活结点计算一个优先级(某些信息的函数值),并根据这些优先级,从当前活结点表中优先选择一个优先级最高(最有利)的结点作为扩展结点,使搜索朝着解空间树上有最优解的分支推进,以便尽快地找出一个最优解。其后,将当前最优解作为一个“标准”,对上界(或下届)不可能达到(或大于)这个“标准”的分支,则不去进行搜索,这样剪枝的效率更高,能较好地缩小搜索范围,从而提高搜索的效率。这便是“优先队列搜索”,即“LC—检索”,的核心思想。
(2)算法设计要点
结点扩展方式:优先队列的分支限界法将活结点组织成一个优先队列,并按照优先队列中规定的结点优先级选取优先级最高的下一个结点成为当前扩展结点。
结点优先级确定:优先队列中结点的优先级通常规定为一个与该结点相关的数值,该数值一般表示以该结点为根的子树中的分支(最优的分支)接近最优解的程度。
优先队列组织:结点优先级确定后,按结点优先级进行排序,就生成了优先队列。
3、算法设计
在装载问题中,以“已装载货物+还未装载货物”,也就是装载上界,作为结点的优先级。仔细想想,装载过程,主要有两个目标,将船装满,将货物装完。为将船装满,船中现有的货物应越多越好,因此“已装载货物”越大越好;为将货物装完,就应该朝着还未装载货物越多的状态进行,因此“还未装载货物”越大越好。因此,以该值为优先级,朝着向最优解的状态进行搜索。
第一步,确定优先队列优先级(大顶堆);
第二步,根据判断条件(该艘船的已装载量+该货物<=该艘船的载重量),构造解空间树。若装载,为左孩子,否则为右孩子。左、右孩子均入优先队列。
第三步,根据结点的优先级,选择优先级最高的结点为扩展结点(最开始解空间树的根结点为扩展结点),根据判断条件为扩展结点添加孩子;
第四步,一直执行第二、三步,直到找到最优解(将船最大量的装载)。
四、代码实现
1、源代码
#include<iostream>
#include<queue>
using namespace std;
class BBNode{
public:
BBNode *parent; //活结点的父结点
bool isLeftChild; //true:左孩子 false:右孩子
};
//优先队列中的结点定义
class MaxHeapQNode{
public:
BBNode *liveNode; //优先队列结点的主要部分-活结点
int upperLimitWeight; //当前的装载上界(结点优先级)=已装载量+未装载货物
int level; //当前结点的层数 叶子结点在第3层
};
//优先队列的比较函数 大顶堆
struct compare{
bool operator()(MaxHeapQNode *&a,MaxHeapQNode *&b) const{
return a->upperLimitWeight < b->upperLimitWeight;
}
};
//添加结点 参数:优先队列,父结点,左孩子标志,优先级(装载上界),层数
void AddLiveNode(priority_queue<MaxHeapQNode*,vector<MaxHeapQNode*>,compare> &q,BBNode *parent,bool isLeft,int upLimit,int level){
BBNode *b=new BBNode; //优先队列结点的主结点-活结点
b->parent = parent;
b->isLeftChild = isLeft;
MaxHeapQNode *h = new MaxHeapQNode; //优先队列结点
h->liveNode=b;
h->upperLimitWeight=upLimit;
h->level=level;
q.push(h); //进入优先列队
}
//装载过程 参数: 货物数,船的载重量,货物重量,装载货物记录
int MaxLoading(int n,int c,int* w,int* &bestx){
priority_queue<MaxHeapQNode *,vector<MaxHeapQNode* >,compare> heap;
int i=0; //解空间树的层数标识
int ew=0; //当前已装载量
int nodePriority=0; //结点优先级——装载上界=已装载量+未装载货物
MaxHeapQNode *h;
BBNode *e;
int* remains; //剩余数组 记录未装载货物
remains=new int[n];
remains[n-1]=0;
for(int j=n-2;j>=0;j--){ //计算当到达指定层时的剩余数组
remains[j]=remains[j+1]+w[j+1];
}
while(i!=n){ //装载过程
if(ew+w[i] <= c){ //判断是否满足装载条件
nodePriority = ew+w[i]+remains[i];
AddLiveNode(heap,e,true,nodePriority,i+1); //左孩子
}
nodePriority=ew+remains[i];
AddLiveNode(heap,e,false,nodePriority,i+1); //右孩子
h = heap.top(); //查询优先队列队头结点
heap.pop(); //队头结点出队
i=h->level;
e=h->liveNode; //记录当前到达的结点
ew=h->upperLimitWeight - remains[i-1]; //计算当前已经装载量
}
// 记录已装载货物
for(int j=n-1;j>=0;j--){
bestx[j]=e->isLeftChild? 1:0;
e=e->parent;
}
return ew; //返回最优装载量
}
int main(){
int cargoNum; //货物数
int shipNum; //货船数量
int bestw; //最优装载量
int sumC=0; //货船总重量
int sumS=0; //货物总重量
printf("请输入货物数: ");
scanf("%d",&cargoNum);
printf("请输入货船的数量: ");
scanf("%d",&shipNum);
int *weight; //货物重量
weight = new int[cargoNum];
int *bestx; //记录已装载货物
bestx = new int[cargoNum];
int *ship; //货船重量
ship = new int[shipNum];
printf("请输入货物的重量:");
for(int i=0;i<cargoNum;i++){
scanf("%d",&weight[i]);
}
printf("请输入货船的载重量:");
for(int i=0;i<shipNum;i++){
scanf("%d",&ship[i]);
}
printf("\n");
//货物总重量
for(int i=0;i<cargoNum;i++){
sumC=sumC+weight[i];
}
//货船总重量
for(int i=0;i<shipNum;i++){
sumS=sumS+ship[i];
}
//货物最大重量
int maxC;
maxC=weight[0];
for(int i=1;i<cargoNum;i++){
if(maxC<weight[i]){
maxC=weight[i];
}
}
//货船最大装载量
int maxS;
maxS=ship[0];
for(int i=i;i<shipNum;i++){
if(maxS<ship[i]){
maxS=ship[i];
}
}
if(maxC>maxS){
printf("货物重量超过货船的载重量,无法装载!");
return 0;
}
if(sumC>sumS){
printf("货物总重量超过货船的总载重量,无法装载!");
return 0;
}
int shipI=0; //货船标识
int cargoI=cargoNum; //货物标识
int count=0; //已装载数量
while(shipNum!=0){
bestw=MaxLoading(cargoI,ship[shipI],weight,bestx);
int *temp; //还未装载货物
temp = new int[cargoNum];
int j=0;
printf("第%d艘船的最优装载量为:%d\n",shipI+1,bestw);
printf("装载的货物为:");
for(int i=0;i<cargoNum;i++){
if(bestx[i]){
printf("%d ",weight[i]);//输出已装载货物
count++;
}else{
temp[j]=weight[i]; //记录未装载货物
j++;
}
}
if(shipNum!=1){
printf("\n此时,还未装载的货物为:");
for(int i=0;i<cargoNum-count;i++){
printf("%d ",temp[i]);
}
}
printf("\n\n");
for(int i=0;i<cargoNum;i++){
weight[i]=0;
bestx[i]=0;
}
for(int i=0;i<cargoNum-count;i++ ){
weight[i]=temp[i];
}
bestw=0;
cargoI=cargoNum-count;
shipNum--;
shipI++;
}
return 0;
}
2、测试用例
货物:10、40、50
货船:60、50
计算剩余数组:
remains[0]=90
remains[1]=50
remains[2]=0
解空间树
(请注意,在解空间树中,左上角的方框为结点的优先级,结点中的数字为该结点状态下货船已装载货物重量)
输出结果
多船多货物测试