问题描述

  • n个作业{1,2,…,n},要在由机器M1和M2组成的流水线上完成加工。
  • 每个作业加工的顺序都是先在M1上加工,然后在M2上加工。
  • M1和M2加工作业i所需的时间分别为ai和bi。
    要求确定这n个作业的最优加工顺序,使得从第一个作业在机器M1上开始加工,到最后一个作业在机器M2上加工完成所需的时间最少。

问题分析

直观上,一个最优调度应使机器M1没有空闲时间,且机器M2的空闲时间最少。

在一般情况下,机器M2上会有机器空闲和作业积压两种情况

流水线作业调度问题 - 图1

设全部作业的集合为N={1,2,…,n}。流水线作业调度问题 - 图2是N的作业子集。

通常,机器M1开始加工S中作业时,机器M2还在加工其它作业,要等时间t后才可利用。

将这种情况下完成S中作业所需的最短时间记为T(S, t)。

流水作业调度问题的最优值为T(N, 0)。

算法思路

直观上,一个最优调度应使机器M1没有空闲时间,且机器M2的空闲时间最少。在一般情况下,机器M2上会有机器空闲和作业积压2种情况。

最优调度应该是:

1. 使M1上的加工是无间断的。即M1上的加工时间是所有ai之和,但M2上不一定是bi之和。

2. 使作业在两台机器上的加工次序是完全相同的。

则得结论:仅需考虑在两台机上加工次序完全相同的调度。

设全部作业的集合为N={1,2,…,n}。S是N的作业子集。在一般情况下,机器M1开始加工S中作业时,机器M2还在加工其他作业,要等时间t后才可利用。将这种情况下完成S中作业所需的最短时间记为T(S,t)。流水作业调度问题的最优值为T(N,0)。

这个T(S,t)该如何理解?举个例子

流水线作业调度问题 - 图3

最优子结构

流水线作业调度问题 - 图4

流水线作业调度问题 - 图5选一个作业i先加工,在M1的加工时间。

流水线作业调度问题 - 图6剩下的作业要等流水线作业调度问题 - 图7时间后才能在M2上加工。注意这里函数的定义,因为一开始工作i是随机取的,M1加工完了流水线作业调度问题 - 图8之后,要开始加工bi了,这里M1是空闲的可以开始加工剩下的N-i个作业了,但此时M2开始加工bi,所以要等流水线作业调度问题 - 图9时间之后才能重新利用,对应到上面函数T(s,t)的定义的话,这里就应该表示成流水线作业调度问题 - 图10, 所以最优解可表示为T(N,0)=min{ai + T(N-{i}, bi)}, i∈N,即我们要枚举所有的工作i,使这个式子取到最小值。

继续分析T(S,t)可得:流水线作业调度问题 - 图11

其中:流水线作业调度问题 - 图12:剩下的作业等流水线作业调度问题 - 图13才能在M2加工,至于这里是怎么推导出来的呢?见下面推导:

流水线作业调度问题 - 图14

最优子结构性质

最优子结构性质:问题最优解,是否包含了子问题的最优解。

流水线作业调度问题 - 图15

这段可以这么理解:设π是所给n个流水作业(N={1,2,…,n})的一个最优调度,最优调度序列是π(1) ,π(2), π(3),…,π(n) ,π是否是调度π(2), π(3),…, π(n)的一个最优调度?若是,最优子结构性质成立。证明如下:

流水线作业调度问题 - 图16

把π调度n个作业所需的加工时间分成两部分: 流水线作业调度问题 - 图17和T’。 其中,T’是机器M1和M2加工作业{π(2),…,π(n)}所需的时间。因此,π调度n个流水作业需要的总时间为流水线作业调度问题 - 图18和T’

令作业子集S=N - {π(1)} ,即:S={π(2), π(3),…,π(n)}。

假设π不是实现加工作业子集S所需时间最短(最优)的调度,设π’是M1和M2加工作业子集S所需时间最短的一个最优调度, 则按π’加工作业子集S的最短时间为$ T(S, b_{π(1)} )$

流水线作业调度问题 - 图19

因此π(1), π’(2),…, π’(n)是完成N ={1,2,…,n}作业 的一个调度,且该调度完成n个作业所需的时间 流水线作业调度问题 - 图20

由于 π’是加工π(2),…,π(n)的最优调度,则T(S,bπ(1))是最短 时间,则流水线作业调度问题 - 图21%7D)%E2%89%A4%20T%E2%80%99#card=math&code=T%28S%2C%20b%7B%CF%80%281%29%7D%29%E2%89%A4%20T%E2%80%99&id=jge8q),因此,![](https://g.yuque.com/gr/latex?a%7B%CF%80(1)%7D%20%2BT(S%2Cb%7B%CF%80(1)%7D)%20%E2%89%A4a%7B%CF%80(1)%7D%2BT%E2%80%99#card=math&code=a%7B%CF%80%281%29%7D%20%2BT%28S%2Cb%7B%CF%80%281%29%7D%29%20%E2%89%A4a_%7B%CF%80%281%29%7D%2BT%E2%80%99&id=hOlEN).由

此,按照π(1), π’(2),…, π’(n)调度顺序完成n个作业所需的时间,小于按照π(1), π(2) ,…, π(n) 调度完成n个作业所需时间流水线作业调度问题 - 图22,这与π是N的最优调度矛盾,

因此,π’是完成π(2),…,π(n)的最优调度假设不成立,因此,π是完成π(2),…,π(n)作业的最优调度。即:作业调度问题最优子结构性质成立。

递归计算最优值

由流水作业调度问题的最优子结构性质可知:

流水线作业调度问题 - 图23

一般情况下:
流水线作业调度问题 - 图24

流水线作业调度问题 - 图25
问题是虽然满足最优子结构性质,也在一定程度满足子问题重叠性质。N的每个非空子集都计算一次,共2n-1次,指数级的。流水线作业调度问题 - 图26

为了解决这个问题引入Johnson不等式

Johnson不等式

流水线作业调度问题 - 图27

推导公式的最后两步,作用是提出流水线作业调度问题 - 图28流水线作业调度问题 - 图29,然后直接max三元素

流水线作业调度问题 - 图30

流水线作业调度问题 - 图31

流水线作业调度问题 - 图32

算法描述

流水线作业调度问题 - 图33

假设有下列的7个作业:

流水线作业调度问题 - 图34

推测一下这个Johson法则为什么能够得到最小的作业时间?

Johson法则分出的第一组都是M2加工时间大于M1的,且按M1时间递增;分出的第二组都是M1加工时间大于M2的,且按M2时间递减。

由于M1加工是无间断的,决定时间长短的只是M2。按照Johson法则会发现,中间部分都是一些M2耗时大的作业,两头都是一些耗时小的作业,个人觉得这样安排会很好填充M2中的时间空隙。

流水线作业调度问题 - 图35

代码演示

  1. #include <iostream>
  2. #include <vector>
  3. #include <algorithm>
  4. using namespace std;
  5. class JOB
  6. {
  7. public:
  8. int t1;
  9. int t2;
  10. };
  11. //第一道工序的升序排列
  12. bool cmp(JOB a,JOB b)
  13. {
  14. return a.t1<b.t1;
  15. }
  16. //第二道工序的降序排列
  17. bool cmp2(JOB a,JOB b){
  18. return a.t2>b.t2;
  19. }
  20. int johnson(const vector<JOB>& parts){
  21. vector<JOB> N1;
  22. vector<JOB> N2;
  23. for(JOB x:parts){
  24. x.t1<=x.t2?N1.emplace_back(x):N2.emplace_back(x);
  25. }
  26. //Johnson调度
  27. //分别排序第一类和第二类
  28. sort(N1.begin(),N1.end(),cmp);
  29. sort(N2.begin(),N2.end(),cmp2);
  30. //合并到一起
  31. vector<JOB> total;
  32. total.insert(total.end(),N1.begin(),N1.end());
  33. total.insert(total.end(),N2.begin(),N2.end());
  34. //计算时间
  35. long t1 = total[0].t1;
  36. long t2 = t1+total[0].t2;
  37. for(vector<JOB>::iterator it = total.begin()+1;it!=total.end();++it){
  38. t1+=(*it).t1;//M1在执行c[i]作业,M1不间断
  39. t2 = max(t2,t1) + (*it).t2;//M1在执行c[i]作业的同时,M2在执行c[i-1]号作业,最短执行时间取决于M1与M2谁后执行完
  40. }
  41. return t2;
  42. }
  43. int main(int argc, char** argv)
  44. {
  45. int N;
  46. cin>>N;
  47. vector<JOB> parts;
  48. for(int i=0;i<N;++i){
  49. JOB tmp;
  50. cin>>tmp.t1>>tmp.t2;
  51. parts.emplace_back(tmp);
  52. }
  53. cout<<johnson(parts)<<endl;
  54. return 0;
  55. }
  56. /*
  57. 7
  58. 5 2
  59. 3 4
  60. 6 7
  61. 4 2
  62. 8 9
  63. 9 7
  64. 6 3
  65. */

结果:43