问题描述
有n个作业(编号为1~n)要在由两台机器M1和M2组成的流水线上完成加工。每个作业加工的顺序都是先在M1上加工,然后在M2上加工。M1和M2加工作业i所需的时间分别为ai和bi(1≤i≤n)。
流水作业调度问题要求确定这n个作业的最优加工顺序,使得从第一个作业在机器M1上开始加工,到最后一个作业在机器M2上加工完成所需的时间最少。可以假定任何作业一旦开始加工,就不允许被中断,直到该作业被完成,即非优先调度。
问题求解
作业编号为1到n,调度方案的执行步骤为1到n,解空间每一层对应一个步骤的作业分配。
根结点对应步骤0(虚),依次为步骤1、2、…、n分配任务,叶子结点对应步骤n。
叶子节点为一个可行解,比较求最优解
对于按1~n顺序执行的某种调度方案,f1表示在M1上执行完当前第i步的作业对应的总时间,f2数组表示在M2上执行完当前第i步的作业的总时间。
若第i步执行作业j,计算公式如下:
f1=f1+a[j];
f2[i]=max(f1,f2[i-1])+b[j]
这里由于每个结点中都保存了f1和f2,因此可以将f2数组改为单个变量。将每个队列结点的类型声明如下:
struct NodeType //队列结点类型
{ int no; //结点编号
int x[MAX]; //x[i]表示第i步分配作业编号
int y[MAX]; //y[i]=1表示编号为i的作业已经分配
int i; //步骤编号
int f1; //已经分配作业M1的执行时间
int f2; //已经分配作业M2的执行时间
int lb; //下界
bool operator<(const NodeType &s) const //重载<关系函数
{
return lb>s.lb; //lb越小越优先出队
}
};
对应的求结点e的lb的算法如下:
void bound(NodeType &e) //求结点e的限界值
{ int sum=0;
for (int i=1;i<=n;i++) //扫描所有作业
if (e.y[i]==0) sum+=b[i];
//仅累计e.x中还没有分配的作业的b时间
e.lb=e.f2+sum;
}
用bestf(初始值为∞)存放最优调度时间。
bestx数组存放当前作业最优调度。
采用的剪枝原则是,仅仅扩展 e.lb<bestf 的结点。
代码
//问题表示
int n=4; //作业数
int a[MAX]={0,5,12,4,8}; //M1上的执行时间,不用下标0的元素
int b[MAX]={0,6,2,14,7}; //M2上的执行时间,不用下标0的元素
//求解结果表示
int bestf=INF; //存放最优调度时间
int bestx[MAX]; //存放当前作业最佳调度
int total=1; //结点个数累计
struct NodeType //队列结点类型
{ int no; //结点编号
int x[MAX]; //x[i]表示第i步分配作业编号
int y[MAX]; //y[i]=1表示编号为i的作业已经分配
int i; //步骤编号
int f1; //已经分配作业M1的执行时间
int f2; //已经分配作业M2的执行时间
int lb; //下界
bool operator<(const NodeType &s) const //重载<关系函数
{
return lb>s.lb; //lb越小越优先出队
}
};
void bound(NodeType &e)
{
int sum=0;
for(int i=1;i<=n;i++)
if(e.y[i]==0)//当前作业未被分配
sum+=b[i];
e.lb=e.f2+sum;
}
void bfs()
{
NodeType e,e1;
priority_queue<NodeType> qu;
memset(e.x,0,sizeof(e.x));
messet(e.y,0,sizeof(e.y));
e.i=0;
e.f1=0;
e.f2=0;
bound(e);
e.no=total++;
qu.push(e);
while(!qu.empty())
{
e=qu.top();
qu.pop();
if(e.i==n)
{
if(e.f2<bestf)
{
bestf=e.f2;
for(int j=1;j<=n;j++)
bestx[j]=e.x[j];
}
}
e1.i=e.i+1;
for(int j=1;j<=n;j++)
{
if(e.y[j]==1)
continue;
for(int i=1;i<=n;i++)
e1.x[i]=e.x[i];
e1.x[e1.i]=j;
for(int i=1;i<=n;i++)
e1.y[i]=e.y[i];
e1.y[j]=1;
e1.f1=e.f1+a[j];
e1.f2=max(e1.f1,e.f2)+b[j];
bound(e1);
if(e1.lb<bestf)
{
e1.no=total++;
qu.push(e1);
}
}
}
}
改进
在扩展每个子结点时判断是否为叶子结点,若是则产生一个可行解,比较产生最优解,该叶子结点不进队;若不是叶子结点则将其进队。
//问题表示
int n=4; //作业数
int a[MAX]={0,5,12,4,8}; //M1上的执行时间,不用下标0的元素
int b[MAX]={0,6,2,14,7}; //M2上的执行时间,不用下标0的元素
//求解结果表示
int bestf=INF; //存放最优调度时间
int bestx[MAX]; //存放当前作业最佳调度
int total=1; //结点个数累计
struct NodeType //队列结点类型
{ int no; //结点编号
int x[MAX]; //x[i]表示第i步分配作业编号
int y[MAX]; //y[i]=1表示编号为i的作业已经分配
int i; //步骤编号
int f1; //已经分配作业M1的执行时间
int f2; //已经分配作业M2的执行时间
int lb; //下界
bool operator<(const NodeType &s) const //重载<关系函数
{
return lb>s.lb; //lb越小越优先出队
}
};
void bound(NodeType &e)
{
int sum=0;
for(int i=1;i<=n;i++)
if(e.y[i]==0)//当前作业未被分配
sum+=b[i];
e.lb=e.f2+sum;
}
void bfs()
{
NodeType e,e1;
priority_queue<NodeType> qu;
memset(e.x,0,sizeof(e.x));
messet(e.y,0,sizeof(e.y));
e.i=0;
e.f1=0;
e.f2=0;
bound(e);
e.no=total++;
qu.push(e);
while(!qu.empty())
{
e=qu.top();
qu.pop();
e1.i=e.i+1;
for(int j=1;j<=n;j++)
{
if(e.y[j]==1)
continue;
for(int i=1;i<=n;i++)
e1.x[i]=e.x[i];
e1.x[e1.i]=j;
for(int i=1;i<=n;i++)
e1.y[i]=e.y[i];
e1.y[j]=1;
e1.f1=e.f1+a[j];
e1.f2=max(e1.f1,e.f2)+b[j];
bound(e1);
if(e1.i==n)//到达叶子节点
{
if(e1.f2<bestf)
{
bestf=e1.f2;
for(int j=1;j<=n;j++)
bestx[j]=e1.x[j];
}
}
else if(e1.lb<bestf)
{
e1.no=total++;
qu.push(e1);
}
}
}
}