要求确定这n个作业的最优加工顺序,使得从第一个作业在机器M1上开始加工,到最后一个作业在机器M2上加工完成所需的时间最少。
直观上,一个最优调度应使机器M1没有空闲时间,且机器M2的空闲时间最少。
设全部作业的集合为N={1,2,…,n}。S ⊆ \subseteq ⊆ N是N的作业子集。
通常,机器M1开始加工S中作业时,机器M2还在加工其它作业,要等时间t后才可利用
。
将这种情况下完成S中作业所需的最短时间
记为T(S, t)
。
流水作业调度问题的最优值为T(N, 0)
。
最优子结构性质:问题最优解,是否包含了子问题的最优解。
调度问题最优子结构性质:设π是所给n个流水作业(N={1,2,…,n})的一个最优调度,最优调度序列是π(1) ,π(2), π(3),…,π(n) ,π是否是调度π(2), π(3),…, π(n)的一个最优调度?
若是,最优子结构性质成立。证明如下:
不是实现加工作业子集S
所需时间最短(最优)的调度,设π’是M1和M2加工作业子集
S所需时间最短的一个最优调度, 则按π’加工作业子集S的最短时间为 T(S, b π ( 1 ) b_{π(1)} bπ(1) )小于
按照π(1), π(2) ,…, π(n) 调度完成n个作业所需时间aπ(1)+T’,这与π是N的最优调度矛盾
,π’是完成π(2),…,π(n)的最优调度假设不成立
,因此,π是完成π(2),…,π(n)作业的最优调度。即:作业调度问题最优子结构性质成立。由流水作业调度问题的最优子结构性质可知:
对于流水作业调度问题,必存在最优调度π ,使得作业π(i)和π(i+1)满足Johnson不等式, 此时称π为满足Johnson法则的调度。
min{ b π ( i ) b_{π(i)} bπ(i), a π ( i + 1 ) a_{π(i+1)} aπ(i+1)}≥min{ b π ( i + 1 ) b_{π(i+1)} bπ(i+1), a π ( i ) a_{π(i)} aπ(i)} , 1≤i ≤n-1
所有满足Johnson法则的调度均为最优调度,且具有相同的加工时间
。从而,将流水作业调度问题转化为求满足Johnson法则的调度问题
。
设 n = 4,
( a 1 a_1 a1, a 2 a_2 a2, a 3 a_3 a3, a 4 a_4 a4)=(3,4,8,10)
( b 1 b_1 b1, b 2 b_2 b2, b 3 b_3 b3, b 4 b_4 b4)=(6,2,9,15)
解:
经排序后为
( b 2 b_2 b2, a 1 a_1 a1, a 2 a_2 a2, b 1 b_1 b1, a 3 a_3 a3, b 3 b_3 b3, a 4 a_4 a4, b 4 b_4 b4)=(2,3,4,6,8,9,10,15)
设σ1,σ2,σ3,σ4是最优调度。
因为最小数是b2,故置σ4= 2。下一个次小的数是a1,置σ1= 1。接下去是a2,作业2已经被调度。再其次是b1作业1也已经被调度。下一个是a3,置σ2= 3,依次置σ3= 4。
红线左侧
满足 a π ( i ) a_{π(i)} aπ(i) ≤ b π ( i ) b_{π(i)} bπ(i) 和 a π ( i ) a_{π(i)} aπ(i) ≤ a π ( i + 1 ) a_{π(i+1)} aπ(i+1) ,符合johnson不等式: min{ b π ( i ) b_{π(i)} bπ(i), a π ( i + 1 ) a_{π(i+1)} aπ(i+1)}≥min{ b π ( i + 1 ) b_{π(i+1)} bπ(i+1), a π ( i ) a_{π(i)} aπ(i)} ,N1中作业调度顺序最优;红线右侧
满足 b π ( i + 1 ) b_{π(i+1)} bπ(i+1) ≤ a π ( i + 1 ) a_{π(i+1)} aπ(i+1) 和 b π ( i + 1 ) b_{π(i+1)} bπ(i+1) ≤ b π ( i ) b_{π(i)} bπ(i),符合johnson不等式: min{ b π ( i ) b_{π(i)} bπ(i), a π ( i + 1 ) a_{π(i+1)} aπ(i+1)}≥min{ b π ( i + 1 ) b_{π(i+1)} bπ(i+1), a π ( i ) a_{π(i)} aπ(i)} ,N2中作业调度顺序最优;中间过渡部分
横向比较,左侧 a π ( i ) a_{π(i)} aπ(i) ≤ b π ( i ) b_{π(i)} bπ(i),右侧 b π ( i + 1 ) b_{π(i+1)} bπ(i+1) ≤ a π ( i + 1 ) a_{π(i+1)} aπ(i+1),符合johnson不等式: min{ b π ( i ) b_{π(i)} bπ(i), a π ( i + 1 ) a_{π(i+1)} aπ(i+1)}≥min{ b π ( i + 1 ) b_{π(i+1)} bπ(i+1), a π ( i ) a_{π(i)} aπ(i)} ,其作业调度顺序最优;
若 a π ( i ) a_{π(i)} aπ(i) ≤ b π ( i + 1 ) b_{π(i+1)} bπ(i+1) , 则 a π ( i ) a_{π(i)} aπ(i) ≤ b π ( i + 1 ) b_{π(i+1)} bπ(i+1) ≤ a π ( i + 1 ) a_{π(i+1)} aπ(i+1) ,又 a π ( i ) a_{π(i)} aπ(i) ≤ b π ( i ) b_{π(i)} bπ(i) , 成立。
若 a π ( i ) a_{π(i)} aπ(i) ≥ b π ( i + 1 ) b_{π(i+1)} bπ(i+1) , 则 b π ( i + 1 ) b_{π(i+1)} bπ(i+1) ≤ a π ( i ) a_{π(i)} aπ(i) ≤ b π ( i ) b_{π(i)} bπ(i) ,又 b π ( i + 1 ) b_{π(i+1)} bπ(i+1) ≤ a π ( i + 1 ) a_{π(i+1)} aπ(i+1) , 成立。
算法的主要计算时间花在对作业集的排序。因此,在最坏情况下算法所需的计算时间为O(nlogn)
。所需的空间为O(n)
。
参照上面的计算步骤进行分析,有些许不同。
class Jobtype
{
public:
int key, index; // key保存ai和bi二者较小的值; index保存作业号i
bool job; ///将满足条件ai
int operator <=(Jobtype a) const
{
return(key<=a.key);
}
};
int FlowShop(int n, int a[], int b[], int c[])
{
Jobtype *d = new Jobtype[n];
for(int i=0; i<n; i++)
{
d[i].key = a[i]>b[i]? b[i]:a[i]; //分别取b[i]和a[i]值较小的作为关键字
d[i].job = a[i]<=b[i]; //将满足a[i]
d[i].index = i; //将当前作业号i赋值给index
}
Sort(d, n);//对数组d按关键字key升序进行排序
int j = 0, k = n-1; //指向数组c的两个指针,j指向最前面,k指向最后面
for(int i=0; i<n; i++)
{
if(d[i].job)
c[j++] = d[i].index; //将排过序的数组d,取N1中作业号,放到数组c的前面
else
c[k--] = d[i].index;//将d中属于N2的作业号, 放到数组c的后面,从而实现N1的非减序排序,N2的非增序排序
}
j = a[c[0]]; //第一个作业a完成的时间
k = j+b[c[0]]; //第一个作业a+b完成的时间
for(int i=1; i<n; i++)
{
j += a[c[i]]; //M1在执行c[i]作业的同时,M2在执行c[i-1]号作业,最短执行时间取决于M1与M2谁后执行完
k = j<k? k+b[c[i]] : j+b[c[i]]; //计算最优加工时间
}
delete d;
return k;
}
把在M1上工作的时间看做是先行工序时间,M2上的工作时间看成后行工序时间。
如果某个作业的M1时间>M2时间,它就是后行工序;反之,就是先行工序时间。
#include
#include
#include
#define n 6 //6个作业
using namespace std;
int M1[n]={2,7,6,4,6,8};
int M2[n]={5,3,2,7,9,2};
int c[n]={0}; //存放次序,注意:c[m]=k,意思是第m+1个执行的作业是k
class Node{
public:
int time; //时间
int index; //来自第几个作业
int position; //是先行工序还是后行工序
};
bool cmp(Node a,Node b){
return a.timeM2[i]){
node[i].time=M2[i];
node[i].position=2; //后行工序
}
else{
node[i].time=M1[i];
node[i].position=1; //先行工序
}
}
//虽然把n个作业都赋值到了Node型结构体中,
//但是大小交错,没有顺序,
//所以需要排序
sort(node,node+n,cmp);
//排完序后,把原本顺序都乱了,先行、后行工作虽然交错,但都已经从小到大排列了
//需要用c数组记录执行顺序,先行工序从前往后放,后行工序从后往前放
for(int i=0;itime2?time1+M2[c[i]]:time2+M2[c[i]];
}
cout<<"次序:"<
输入
数据已经在代码中给出
int M1[n]={2,7,6,4,6,8};
int M2[n]={5,3,2,7,9,2};