0x09算法设计与分析复习(二):算法设计策略-分枝限界法3
参考书籍:算法设计与分析——C++语言描述(第二版)
算法设计策略-分枝限界法
批处理作业调度
问题描述
设有n个作业的集合
分枝限界法求解
这一问题的可行解是n个作业所有可能的排列,每一种排列代表一种作业调度方案S,其目标函数是
设结点X是状态空间树上地一个结点,它对应一个已经调度地作业子集
设
(1)先考虑方案
作业i接着还需在
那么,所有剩余作业的完成时间之和为:
(2)再看方案
式中,
一般对于任意给定的调度方案S,总有
可以证明,当
与回溯法相同,可以用最优解的上界值U进行剪枝。如果对于已经调度的作业子集的某种排列,所需的完成时间和已经大于迄今为止所记录下的调度方案中最短完成时间之和U,则该分枝应剪去。
批处理作业调度算法
//批作业类和活结点结构
struct Node{
Node(int sz)
{
n=sz;
x=new in[n];
for(int i=0;i<n;i++)
x[i]=i;
k=f1=f2=sf2=0;
}
Node(Node e,int ef1,int ef2)
{
//e是当前结点的双亲结点,ef1和ef2是新结点的f1和f2值
n=e.n;
x=new int[n];
for(int i=0;i<n;i++)
x[i]=e.x[i];
f1=ef1;
f2=ef2;
sf2=e.sf2+f2;
k=e.k+1;
}
void Swap(int i,int j);
int k,n;//已调度的作业数和作业总数
int f1,f2;//当前调度作业在两台设备上的最后完成时间
int sf2;//已调度的作业的累计完成时间之和
int *x;//当前作业调度方案下,当前结点代表的部分解向量
};
struct pqNode{
//活结点结构
operator int()const
{
return LBB;
}
pqNode(){};
pqNode(int lb,Node *p)
{
LBB=lb;
ptr=p;
}
int LBB;//完成时间和的下界函数值
Node *ptr;//ptr指示相应的活结点
};
class BatchJob{
public:
BatchJob(int sz,int *aa,int *bb)
{
n=sz;
a=new int[n];
b=new int[n];
p=new int[n];
for(int i=0;i<n;i++){
a[i]=aa[i];
b[i]=bb[i];
}
Sort(b,p,n);//p为下标的一种排列,使得b[p[i]]<=b[p[i+1]]
}
int JobSch(int *x);//分枝限界法求最优解,并返回最优解值
private:
int LBound(Node e,int &f1,int &f2);//计算e的一个孩子的下界函数值
int *a,*b;//假定作业按a的非减次序排列
int n,*p;
};
//下界函数
int BatchJob::LBound(Node e,int &f1, int &f2)
{
//假定已调度的作业为J=(x[0],x[1],...,x[k-1]),现在考察第x[k]
bool *y1=new bool[n];
for(int j=0;j<n;j++)
y1[j]=false;
for(j=0;j<e.k;j++)
y1[e.x[j]]=true;
f1=e.f1+a[e.x[e.k]];//f1为第k个作业在第一台设备上的完成时间
f2=((f1>e.f2)?f1:e.f2)+b[e.x[e.k]];//f2为第k个作业在第二台设备上的完成时间
int sf2=e.sf2+f2;
int t1=0,t2=0,k1=n-e.k,k2=n-e.k,f3;
for(j=0;j<n;j++){
//计算T_1'
if(!y1[j]){
k1--;
if(k1==n-e.k-1)
f3=(f2>f1+a[j])?f2:f1+a[j];
t1+=f1+k1*a[j]+b[j];
}
}
for(j=0;j<n;j++){
//计算T_2'
if(!y1[p[j]]){
k2--;
t2+=f3+k2*b[p[j]];
}
}
delete y1;
return sf2+((t1>t2)?t1:t2);//返回下界sf2+max{T_1'+T_2'}
}
//批处理作业调度LCBB算法
int BatchJob::JobSch(int *x)
{
Node *e=new Node(n);
pqNode pe(0,e);
int LBB,f1,f2,U=maxint;
PrioQueue<pqNode> q(mSize);//生成一个优先权队列
do{
e=pe.ptr;
if((e->k==n)&&(e->sf2<U)){
U=e->sf2;
for(int i=0;i<n;i++){
x[i]=e->x[i];
//求得一个(更优的)解
}
delete[] e;
} else {
for(int j=e->k;j<n;j++){
Swap(e->x[e->k],e->x[j]);//产生各种排列
LBB=LBound(*e,f1,f2);//计算下界函数值
if(LBB < U){
Node *child=new Node(*e,f1,f2);//扩展一个结点
pqNode pc(LBB,child);//相应的优先权队列的结点
q.Append(pc);//活结点进入优先权队列
}
Swap(e->x[j],e->x[e->k]);
}
delete[] e;
}
if(q.IsEmpty())
return 0;
q.Swerve(pe);
}while(pe.LBB<U);
return U;//返回最优解值
}
函数LBound
计算每个结点的下界函数值。若作业i当前已调度,则y1[i]
和y2[i]
为true,否则为false。f1为第k个作业在第一台设备的完成时间,k1和k2分别是剩余的作业数目。sf2为前k+1个作业累计完成时间之和。函数先计算
函数JobSch
为批处理作业调度的LC分枝限界法。它使用优先权队列作为活结点表。在状态空间树中的每个状态结点处,算法生成x[k]的各种可能取值。