0x09算法设计与分析复习(二):算法设计策略-分枝限界法3

参考书籍:算法设计与分析——C++语言描述(第二版)

算法设计策略-分枝限界法

批处理作业调度

问题描述

设有n个作业的集合{0,1,,n1},每个作业有两个项任务要求依次在两台设备P1P2上完成,aibi分别表示作业i在设备P1P2上的处理时间。批处理作业调度问题是求使得所有作业的完成时间之和FT(S)=n1i=0fi(S)最小的调度方案。

分枝限界法求解

这一问题的可行解是n个作业所有可能的排列,每一种排列代表一种作业调度方案S,其目标函数是FT(S)=n1i=0fi(S)。批处理作业调度问题的状态空间树是一颗排序树,排序树上每个叶结点代表一个可行解的答案结点。为了使用分枝限界法求解,必须对状态空间树中结点定义适当的上下界函数,使其能够有效地进行剪枝。

设结点X是状态空间树上地一个结点,它对应一个已经调度地作业子集J={0,1,,k}。此时J中作业已经调度。iJfi(S)为J中作业地完成时间之和,iJfi(S)为剩余作业地完成时间之和。每个叶结点代表地一种调度方案S地目标函数值FT(S),有:

FT(S)=iJfi(S)+iJfi(S),J={0,1,,k}

f1k是J中作业在设备P1上的完成时间,f2k是J中作业在P2上的完成时间。现在有两种调度方案S1S2。在方案S1下调度剩余作业,使得设备P1没有任何空闲,在方案S2下调度剩余作业,使得设备P2没有任何空闲。

(1)先考虑方案S1。尚未安排的作业i(k<i<n)在设备P1上的完成时间为:

f1k+ak+1+ak+2++ai

作业i接着还需在P2上处理所需时间为bi的任务,因此,作业i的完成时间为:
f1k+ak+1+ak+2++ai+bi

那么,所有剩余作业的完成时间之和为:
T1=i=k+1n1fi(S1)=i=k+1n1[f1k+(ni)ai+bi]

(2)再看方案S2。所有剩余作业的完成时间之和为:
T2=i=k+1n1fi(S2)=i=k+1n1[max(f2k,f1k+miniJ{ai})+(ni)bi]

式中,f1kf2k分别是J中作业在设备P1P2上的完成时间。那么第k+1个作业在设备P2上可开始的时间至少为max(f2k,f1k+miniJai)。根据方案S2,设备P2没有闲置时间。

一般对于任意给定的调度方案S,总有iJfi(S)T1iJfi(S)T2,因此

FT(S)iJfi(S)+max{T1,T2}

可以证明,当ajaj+1(k<j<n1)时,T1取得最小值T1。同理,当bjbj+1(k<j<n1)时,T2取得最小值T2。因此,可将结点X的下界c^(X)定义为:
c^(X)=iJfi(S)+max{T1,T2}

与回溯法相同,可以用最优解的上界值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个作业累计完成时间之和。函数先计算T1,然后计算T2,最后返回结点的下界函数值sf2+max{T1T2}。

函数JobSch为批处理作业调度的LC分枝限界法。它使用优先权队列作为活结点表。在状态空间树中的每个状态结点处,算法生成x[k]的各种可能取值。

原文地址:https://www.cnblogs.com/born2run/p/9581363.html