问题描述
- n个作业{1,2,…,n},要在由机器M1和M2组成的流水线上完成加工。
- 每个作业加工的顺序都是先在M1上加工,然后在M2上加工。
- M1和M2加工作业i所需的时间分别为ai和bi。
要求确定这n个作业的最优加工顺序,使得从第一个作业在机器M1上开始加工,到最后一个作业在机器M2上加工完成所需的时间最少。
问题分析
直观上,一个最优调度应使机器M1没有空闲时间,且机器M2的空闲时间最少。
在一般情况下,机器M2上会有机器空闲和作业积压两种情况

设全部作业的集合为N={1,2,…,n}。是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)该如何理解?举个例子

最优子结构
。
:选一个作业i先加工,在M1的加工时间。
:剩下的作业要等
时间后才能在M2上加工。注意这里函数的定义,因为一开始工作i是随机取的,M1加工完了
之后,要开始加工bi了,这里M1是空闲的可以开始加工剩下的N-i个作业了,但此时M2开始加工bi,所以要等
时间之后才能重新利用,对应到上面函数T(s,t)的定义的话,这里就应该表示成
, 所以最优解可表示为T(N,0)=min{ai + T(N-{i}, bi)}, i∈N,即我们要枚举所有的工作i,使这个式子取到最小值。
继续分析T(S,t)可得:
其中::剩下的作业等
才能在M2加工,至于这里是怎么推导出来的呢?见下面推导:

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

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

把π调度n个作业所需的加工时间分成两部分: 和T’。 其中,T’是机器M1和M2加工作业{π(2),…,π(n)}所需的时间。因此,π调度n个流水作业需要的总时间为
和T’
令作业子集S=N - {π(1)} ,即:S={π(2), π(3),…,π(n)}。
假设π不是实现加工作业子集S所需时间最短(最优)的调度,设π’是M1和M2加工作业子集S所需时间最短的一个最优调度, 则按π’加工作业子集S的最短时间为$ T(S, b_{π(1)} )$

因此π(1), π’(2),…, π’(n)是完成N ={1,2,…,n}作业 的一个调度,且该调度完成n个作业所需的时间
由于 π’是加工π(2),…,π(n)的最优调度,则T(S,bπ(1))是最短 时间,则%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),因此,%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个作业所需时间,这与π是N的最优调度矛盾,
因此,π’是完成π(2),…,π(n)的最优调度假设不成立,因此,π是完成π(2),…,π(n)作业的最优调度。即:作业调度问题最优子结构性质成立。
递归计算最优值
由流水作业调度问题的最优子结构性质可知:

一般情况下:

问题是:虽然满足最优子结构性质,也在一定程度满足子问题重叠性质。N的每个非空子集都计算一次,共2n-1次,指数级的。
为了解决这个问题引入Johnson不等式
Johnson不等式

推导公式的最后两步,作用是提出和
,然后直接max三元素



算法描述

假设有下列的7个作业:

推测一下这个Johson法则为什么能够得到最小的作业时间?
Johson法则分出的第一组都是M2加工时间大于M1的,且按M1时间递增;分出的第二组都是M1加工时间大于M2的,且按M2时间递减。
由于M1加工是无间断的,决定时间长短的只是M2。按照Johson法则会发现,中间部分都是一些M2耗时大的作业,两头都是一些耗时小的作业,个人觉得这样安排会很好填充M2中的时间空隙。

代码演示
#include <iostream>#include <vector>#include <algorithm>using namespace std;class JOB{public:int t1;int t2;};//第一道工序的升序排列bool cmp(JOB a,JOB b){return a.t1<b.t1;}//第二道工序的降序排列bool cmp2(JOB a,JOB b){return a.t2>b.t2;}int johnson(const vector<JOB>& parts){vector<JOB> N1;vector<JOB> N2;for(JOB x:parts){x.t1<=x.t2?N1.emplace_back(x):N2.emplace_back(x);}//Johnson调度//分别排序第一类和第二类sort(N1.begin(),N1.end(),cmp);sort(N2.begin(),N2.end(),cmp2);//合并到一起vector<JOB> total;total.insert(total.end(),N1.begin(),N1.end());total.insert(total.end(),N2.begin(),N2.end());//计算时间long t1 = total[0].t1;long t2 = t1+total[0].t2;for(vector<JOB>::iterator it = total.begin()+1;it!=total.end();++it){t1+=(*it).t1;//M1在执行c[i]作业,M1不间断t2 = max(t2,t1) + (*it).t2;//M1在执行c[i]作业的同时,M2在执行c[i-1]号作业,最短执行时间取决于M1与M2谁后执行完}return t2;}int main(int argc, char** argv){int N;cin>>N;vector<JOB> parts;for(int i=0;i<N;++i){JOB tmp;cin>>tmp.t1>>tmp.t2;parts.emplace_back(tmp);}cout<<johnson(parts)<<endl;return 0;}/*75 23 46 74 28 99 76 3*/
结果:43
