描述:n个作业{1,2,3....,n}要在两台机器M1,M2组成的流水线上加工完成。每个作业的加工顺序都是先在M1上加工,然后在M2上作业加工。M1,M2加工作也i所需的时间分别为ai和bi(1<=i<=n)。流水作业调度问题要求确定这n个作业的最优加工顺序,使得从第一个作业在机器M1上开始加工,到最后一个作业在机器M2上加工完成所需的时间最少。
一个最优调度应该使机器M1没有空闲时间,且机器M2的空闲时间最少,一般情况下,机器M2上会出现 机器空闲 和 作业积压 两种情况。用m(a,b)表示m任务需要在M1机器加工a分钟,在M2上需要b分钟。更直观的看,上述情况可由下图体现出来。
如图所示,如果先加工m1后加工m2,需要时间9t;反过来,需要时间13t,因此第一种情况更优
一定要该清楚每个变量代表着什么意思,要不一会儿就懵了!
设全部作业集合N={1,2,3,...,m},SN是N 的作业子集,一般情况下,机器M1开始加工S中的作业时,机器M2还在加工其他作业,需要等时间t后才可利用。将这种情况下完成S中作业所需最短时间记为T(S,t)。该问题的最优值为T(N,0)。
下面开始动态规划的四个步骤进行分析:
1. 最优子结构
设π是所给n个流水作业的一个最优调度,所需的加工时间为aπ(1)+T`。其中,T`是在机器M2的等待时间为bπ(1)时,安排作业π(2),...,π(n)所需的时间。记S=N-{π(1)},则有T`=T(S,bπ(1))。可说明其具有最优子结构性质。
选题目中前三个为例,更直观的说明一下(反正我看这些表达式是懵的,还是举个例子比较清晰)
2. 递归计算最优值
由最优子结构性质可得T(N,0)=min{ai+T(N-{i},bi)} <1<=i<=n>推广到T(S,t),有:
T(S,t)=min{ai + T(S-{i},bi + max{t-ai},0) }(i∈S)
max{t-ai} 是由于机器M2上,作业i必须在max{t-ai}时间后才能开工。因此在机器M1上完成作业i之后,在机器上还需bi+max{t-ai}-ai=bi+max{t-ai, 0}时间,才能完成作业 i 的加工。(这个数学公式又是为什么呢?具体过程如下图所示。)
虽然满足最优子结构性质,也在一定程度满足子问题重叠性质。N的每个非空子集S都计算一次,共次,达到了指数级。因此引入 Johnson法则 解决这个问题。
3. 流水调度的Johnson法则
这个推导过程看不懂没关系,只要知道:
如果作业i和j满足min{bi,ai}>=min{bj,ai},则称作业i,j满足Johnson不等式;如果i和j不满足不等式,则交换i,j的加工顺序后,既满足 Johnson不等式
这一大串分析只为了得出一个结论:流水作业调度问题一定存在满足Johnson法则的最优调度。
4. 算法描述
上面的推导实在看着头疼,记住结论就好:
将对应的ai和bi放在一起;将ai>bi的放在一起,ai<bi的放在一起;然后ai小的递增排,ai大得递减排就可了。(ai大小是相对bi来说的)
来一个具体问题康康吧!
动态规划的思路,类似矩阵连乘
Johnson算法:
1)先执行t[i,1]<t[i,2],保证M2上没有等待。选择第一个作业时,让M2选择第一次等待时间最少的,
即t[i,1]越小,越靠前执行;
2)执行t[i,1]>=t[i,2]时,要保证最后一个作业在M2上执行时间最短,所以按照减序排列
具体代码如下:
#include <iostream>
#include <fstream>
#include <algorithm>
using namespace std;
const int N=50;
class Job { //作业类
public:
int index, time;//作业编号和时间
bool M;//true代表机器M1, false代表机器M2
};
bool cmp(Job a,Job b){ //升序排序
return a.time<b.time;
}
int* Johnson(int n,int a[],int b[],int best[]){ //best为最优调度的作业序号
int i,j,k,t;
Job *Min =new Job[n];//n个作业中,每个作业的最小加工时间
for(i=0;i<n;i++){
if(a[i]<b[i]){//ai<bi的分到N1组
Min[i].M =true;
Min[i].time =a[i];
}
else{//ai>=bi的分到N2组
Min[i].M=false;
Min[i].time=b[i];
}
Min[i].index=i;
}
sort(Min, Min+n, cmp);//增序排列
j=0,k=n-1;
for(i=0;i<n;i++){
if(Min[i].M ==true)//将排过序的数组Min,取其中作业序号属于N1的从前面进入,实现N1递增排序
best[j++]=Min[i].index;
else//取其中序号属于N2的从后面进入,实现N2递减排序
best[k--]=Min[i].index;
}
t=a[best[0]];//第一件事在M1上的作业时间
k=t+b[best[0]];//第一段时间+最后一段时间
for(j=1;j<n;j++){//通过举例找出其数学计算
t=t+a[best[j]];
k= t<k ? k+b[best[j]] : t+b[best[j]];
}
best[i+1]=k;
delete Min;
return best;//最优序列
}
int main()
{
int i,n,a[N],b[N],best[N];
ifstream infile;
ofstream outfile;
infile.open("input1.txt");
infile>>n;
for(i=0;i<n;i++)
infile>>a[i];
for(i=0;i<n;i++)
infile>>b[i];
infile.close();
outfile.open("output1.txt");
outfile<<"the best order is:"<<endl;
Johnson(n,a,b,best);
for(i=0;i<n;i++){
outfile<<best[i]+1<<" ";
}
outfile<<endl<<"the best time is:"<<endl;
outfile<<best[i+1];
outfile.close();
return 0;
}
读取的输入文件如下图所示:
运行后输出文件如下图所示:
时间复杂度分析:计算时间花在对作业的排序上,最坏情况下,为O(logn),空间0(n)