问题描述:
n个作业{1,2,…,n}
要在由2台机器M1和M2组成的流水线上完成加工。每个作业加工的顺序都是先在M1上加工,然后在M2上加工。M1和M2加工作业i所需的时间分别为ai和bi。流水作业调度问题要求确定这n个作业的最优加工顺序,使得从第一个作业在机器M1上开始加工,到最后一个作业在机器M2上加工完成所需的时间最少。
分析:
直观上,一个最优调度应使机器M1没有空闲时间,且机器M2空闲时间最少。一般情况下,机器M2上会有机器空闲和作业积压2种情况。
设全部作业的集合为N={1,2,…,n}。SN是N的作业子集。在一般情况下,机器M1开始加工S中作业时,机器M2还在加工其他作业,要等时间t后才可利用。将这种情况下完成S中作业所需的最短时间记为T(S,t)。流水作业调度问题的最优值为T(N,0)。
其中a[i]
是第i
个作业在M1上工作的时间,b[i]
是第i
个作业在M2上工作的时间,同一作业分别在两个机器上的工作时间不一定相同
针对这两个递归等式,如果觉得不易理解,请看如下图示。
流水作业调度思想三大步骤:
流水作业调度程序实现四大步骤
①令N1={i|a[i]< b[i]},N2={i|a[i]>=b[i]}
②按key值升序排序
③连接N1&N2
④计算总时间
例如:*
表示选择某作业时该选择比较的key值
作业 | J0 | J1 | J2 | J3 | J4 | J5 |
---|---|---|---|---|---|---|
M1 | 30* | 120 | 50* | 20* | 90 | 110 |
M2 | 80 | 100* | 90 | 60 | 30* | 10* |
则 N1 = {J3,J0,J2}(升序);N2 = {J1,J4,J5}(降序)
public class test3_8 {
public static int flowShop(int[] M1,int[] M2,int[] c){
int n = M1.length;
Element[] d = new Element[n]; //作业元素
//1.令N1={i|a[i]<b[i]},N2={i|a[i]>=b[i]}
for(int i=0;i<n;i++){
int key = (M1[i]<M2[i])? M1[i]:M2[i];
int index = i;
boolean job = (M1[i]<=M2[i]); //job=1表示N1,job=0表示N2
d[i] = new Element(key,index,job);
}
//2.按key值升序排序
bobleSort(d);
//3.连接N1&N2
int count=0;
int k = n-1;
for(int i=0;i<n;i++){
if(d[i].job) c[count++] = d[i].index; //如果是N1
else c[k--] = d[i].index; //如果是N2
}
//4.计算总时间
int time1 = M1[c[0]];
int time2 = time1+M2[c[0]];
for(int i=1;i<n;i++){
time1 += M1[c[i]];
//此处不能是 +=
time2 = (time1<time2)?(time2+M2[c[i]]):(time1+M2[c[i]]);
}
return time2;
}
//整个数组无需分N1或N2排序,整体升序排序也保证N1&N2内部为升序排序
public static void bobleSort(Element[] d){
int n = d.length;
Element temp;
for(int i=0;i<n-1;i++){
boolean ok = true;
for(int j=1;j<n-i;j++){
if(d[j-1].key>d[j].key){
temp = d[j];
d[j] = d[j-1];
d[j-1] = temp;
ok = false;
}
}
if(ok) break;
}
}
public static void main(String[] args) {
int n = 6;
int time; //最优作业调度花费总时间
int[] c = new int[n]; //作业调度顺序
int[] M1 = {30,120,50,20,90,110}; //在M1机器上的工作时间
int[] M2 = {80,100,90,60,30,10}; //在M2机器上的工作时间
System.out.print("M1机器上顺序工作时间为:");
for(int i=0;i<n;i++) System.out.print("("+i+")"+M1[i]+" ");
System.out.println();
System.out.print("M2机器上顺序工作时间为:");
for(int i=0;i<n;i++) System.out.print("("+i+")"+M2[i]+" ");
System.out.println();
time = flowShop(M1,M2,c);
System.out.println("完成作业最短时间为:"+time);
System.out.print("作业最优调度顺序为:");
for(int i=0;i<n;i++)
System.out.print(c[i]+" ");
System.out.println();
}
}
//作业元素(key,index,job)
class Element{
int key; //排序关键码
int index; //作业序号
boolean job; //N1&N2标识符
public Element(int k,int i,boolean j){
this.key = k;
this.index = i;
this.job = j;
}
}
运行结果如下:
M1机器上顺序工作时间为:(0)30 (1)120 (2)50 (3)20 (4)90 (5)110
M2机器上顺序工作时间为:(0)80 (1)100 (2)90 (3)60 (4)30 (5)10
完成作业最短时间为:430
作业最优调度顺序为:3 0 2 1 4 5
补充:以上代码用的排序算法是冒泡排序,大家可以更换成快速排序,这样可以使本算法时间复杂度降到最低,即O(nlogn),因为时间复杂度最高的就是排序代码段,除排序以外代码段都是一个for循环,该算法所需空间为O(n)。
附C++代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
typedef struct JobUnit{
long T1; // 作业在第1个机器上的时间
long T2; // 作业在第2个机器上的时间
};
class Solution{
static bool cmp(JobUnit a, JobUnit b){
bool aIsP = a.T1 < a.T2; // 若作业a在第1台机器的耗时小于在第2台机器的耗时,则用小者做比较
bool bIsP = b.T1 < b.T2;
if(aIsP && bIsP) // 若两个作业的key值都是第1台机器的耗时,则按升序排序
return a.T1 < b.T1;
else if(!aIsP && !bIsP) // 若两个作业的key值都是第2台机器的耗时,则按降序排序
return a.T2 > b.T2;
else if(aIsP && !bIsP) // 若第1个作业的key值是第1台机器,第2个作业的key值时第2台机器,则不交换
return true;
return false; // 若第1个作业的key值是第2台机器,第2个作业的key值时第1台机器,则交换位置
}
public:
long jobSchedule(vector<JobUnit>& job, int n){
// 1.划分N1和N2并按不同key值排好序
sort(job.begin(), job.end(), cmp);
// 2.计算最低总耗时
for(int i = 0;i < n;i++){
minTime += job[i].T1;
if(i > 0)
// 1)之前所有任务在第二台机子的累计剩余、2)前一个任务在第二台机子上的剩余和3)当前任务在第二台机子上的耗时取最大
remainTime = max(remainTime - job[i].T1, max((job[i-1].T2 - job[i].T1), job[i].T2));
else
remainTime = job[i].T2;
}
return minTime + remainTime;
}
private:
long minTime = 0, remainTime;
};
int main(){
int n;
JobUnit jobunit;
vector<JobUnit> job;
while(cin.peek() != '\n'){
scanf("%ld", &jobunit.T1);
job.push_back(jobunit);
}
n = job.size();
for(int i = 0;i < n;i++)
scanf("%ld", &job[i].T2);
Solution sol;
cout<<sol.jobSchedule(job, n)<<endl;
return 0;
}