1、問題描述:
n個作業{1,2,…,n}要在由2台機器M1和M2組成的流水線上完成加工。每個作業加工的順序都是先在M1上加工,然后在M2上加工。M1和M2加工作業i所需的時間分別為ai和bi。流水作業調度問題要求確定這n個作業的最優加工順序,使得從第一個作業在機器M1上開始加工,到最后一個作業在機器M2上加工完成所需的時間最少。
2、問題分析
直觀上,一個最優調度應使機器M1沒有空閑時間,且機器M2的空閑時間最少。在一般情況下,機器M2上會有機器空閑和作業積壓2種情況。設全部作業的集合為N={1,2,…,n}。S是N的作業子集。在一般情況下,機器M1開始加工S中作業時,機器M2還在加工其他作業,要等時間t后才可利用。將這種情況下完成S中作業所需的最短時間記為T(S,t)。流水作業調度問題的最優值為T(N,0)。
設π是所給n個流水作業的一個最優調度,它所需的加工時間為 aπ(1)+T’。其中T’是在機器M2的等待時間為bπ(1)時,安排作業π(2),…,π(n)所需的時間。
記S=N-{π(1)},則有T’=T(S,bπ(1))。
證明:事實上,由T的定義知T’>=T(S,bπ(1))。若T’>T(S,bπ(1)),設π’是作業集S在機器M2的等待時間為bπ(1)情況下的一個最優調度。則π(1),π'(2),…,π'(n)是N的一個調度,且該調度所需的時間為aπ(1)+T(S,bπ(1))<aπ(1)+T’。這與π是N的最優調度矛盾。故T’<=T(S,bπ(1))。從而T’=T(S,bπ(1))。這就證明了流水作業調度問題具有最優子結構的性質。
由流水作業調度問題的最優子結構性質可知:
從公式(1)可以看出,該問題類似一個排列問題,求N個作業的最優調度問題,利用其子結構性質,對集合中的每一個作業進行試調度,在所有的試調度中,取其中加工時間最短的作業做為選擇方案。將問題規模縮小。公式(2)說明一般情況下,對作業集S進行調度,在M2機器上的等待時間,除了需要等該部件在M1機器上完成時間,還要沖抵一部分原來的等待時間,如果沖抵已成負值,自然仍需等待M1將作業做完,所以公式取max{t-ai,0}。
3、動態規划法求解思路
假設有一組作業需要在M1和M2 兩台機器上進行流水作業,他們在M1和M2上的作業時間如下表:
問題是如何安排他們的加工順序,使得,到最后一個作業在機器M2上加工完成所需要的時間最少。也就是所有作業在兩台機器全部加工完成所需的時間最少。
思路如下:考慮如果只有一個作業的情況,肯定所需時間就是它自身需要在M1和M2 上的加工時間總和;如果有兩個作業就要考慮在兩種不同的加工順序下選取最優的一種作為候選,三個作業的時會出現三種組合情況(0,(1,2)); (1,(0,2)); (2,(0,1)),拿第一種為例,它表示先加工作業0,然后再按照作業1和作業2的優化順序加工;將三種的作業時間計算出來,取最小值,即為三個作業的優化結果,同理可對更多的作業進行排序優化。具體做法是,用類似矩陣連乘的辦法,自底向上將所有能的情況計算出來,並產生一個表,供后面的計算查用,減少重復計算的工作量。
對於j1 作業M2 的等待時間為b0,實際上在M2加工j0作業的同時,M1 並行加工j1,實際它需要等待b1-a0時間。
2+4+(5-4)+2=9
從J0和J1兩個作業的加工順序,可以看出,先加工J0后J1,所用時間最短為9,將其填入表中,依此類推,即可得出最優解。
a4+a0+a2+a1+a3+[(b4+b0+b1+b2)-(a0+a1+a2+a3)]+b3
=1+2+3+4+6+[(7+5+2+3)-(2+4+3+6)]+1
=16+[17-15]+1=19
選其中加工時間短的作為候選方案;在具體計算時非最優子集不必考慮,這樣可以減少計算次數。
4、流水作業調度的Johnson法則
設兀是作業集S在機器M2的等待時間為t時的任一最優調度。若在這個調度中,安排在最前面的兩個作業分別是i 和j ,即π(1)=I,π(2)=j。則有動態規划遞歸式可得
其中
如果作業i和j滿足min{bi,aj} ≥min{bj,ai},則稱作業i和j滿足Johnson不等式。如果作業i和j 不滿足Johnson不等式,則交換作業i和j滿足Johnson不等式。
證明 :在作業集S中,對於機器M2 的等待時間為t的調度π,交換作業i和j 的加工順序,得到作業集S 的另一個調度π’,它所需的加工時間為
當作業i和j 滿足Johnson 不等式 min{bi,aj} ≥min{bj,ai}時,有
從而
由此可得
因此,對任意t 有
從而,tij≤tji,由此可見,換句話說,當作業i 和j不滿足Johnson 不等式時,交換它們的加工順序后,作業i和j滿足Johnson 不等式,且不增加加工時間。由此可知,對於流水作業調度問題,必存在最優調度π,使得作業π(i)和π(i+1)滿足Johnson 不等式:
這樣的調度π稱為滿足Johnson 法則的調度。進一步還可以證明,調度滿足Johnson 法則當且僅當對任意i<j 有:
由此可知,任意兩個滿足Johnson 法則的調度具有相同的加工時間,從而所有滿足Johnson 法則的調度均為最優調度。
5、流水作業調度問題Johnson算法
從上面的分析可知,流水作業調度問題一定存在滿足Johnson法則的最優調度,且容易由下面的算法確定:
流水作業調度問題的Johnson算法:
(1)令N1={i|ai<bi},N2={i|ai>=bi};
(2)將N1中作業按ai的非減序排序;將N2中作業按bi的非增序排序;
(3)N1中作業接N2中作業構成滿足Johnson法則的最優調度。
Johnson算法中分類及排序的作用(驗證不等式)設數組c[]為排序后的作業排列,排序結果如下:
紅線左側滿足 a[c[i]]<=b[c[i]] 和 a[c[i]]<=a[c[i+1]] 符合johnson 不等式,min(b[c[i]],a[c[i+1]])>=min(b[c[i+1]],a[c[i]])其調度順序最優;
紅線右側滿足 b[c[i]]<=a[c[i]] 和 b[c[i]]>=b[c[i+1]] 符合johnson 不等式,min(b[c[i]],a[c[i+1]])>=min(b[c[i+1]],a[c[i]])其調度順序最優;
中間過渡部分橫向比較,左側a[c[i]]< b[c[i]] 右側b[c[i+1]]<=a[c[i+1] ]滿足min(b[c[i]],a[c[i+1]])>=min(b[c[i+1]],a[c[i]])其調度順序也最優;
程序具體代碼如下:
//3d9 動態規划 流水作業調度問題 #include "stdafx.h" #include <iostream> using namespace std; const int N = 5; class Jobtype { public: int operator <=(Jobtype a) const { return(key<=a.key); } int key,index; bool job; }; int FlowShop(int n,int a[],int b[],int c[]); void BubbleSort(Jobtype *d,int n);//本例采用冒泡排序 int main() { int a[] = {2,4,3,6,1}; int b[] = {5,2,3,1,7}; int c[N]; int minTime = FlowShop(N,a,b,c); cout<<"作業在機器1上的運行時間為:"<<endl; for(int i=0; i<N; i++) { cout<<a[i]<<" "; } cout<<endl; cout<<"作業在機器2上的運行時間為:"<<endl; for(int i=0; i<N; i++) { cout<<b[i]<<" "; } cout<<endl; cout<<"完成作業的最短時間為:"<<minTime<<endl; cout<<"編號從0開始,作業調度的順序為:"<<endl; for(int i=0; i<N; i++) { cout<<c[i]<<" "; } cout<<endl; return 0; } 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];//按Johnson法則分別取對應的b[i]或a[i]值作為關鍵字 d[i].job = a[i]<=b[i];//給符合條件a[i]<b[i]的放入到N1子集標記為true d[i].index = i; } BubbleSort(d,n);//對數組d按關鍵字升序進行排序 int j = 0,k = n-1; for(int i=0; i<n; i++) { if(d[i].job) { c[j++] = d[i].index;//將排過序的數組d,取其中作業序號屬於N1的從前面進入 } else { c[k--] = d[i].index;//屬於N2的從后面進入,從而實現N1的非減序排序,N2的非增序排序 } } j = a[c[0]]; k = j+b[c[0]]; 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; } //冒泡排序 void BubbleSort(Jobtype *d,int n) { int i,j,flag; Jobtype temp; for(i=0;i<n;i++){ flag = 0; for(j=n-1;j>i;j--){ //如果前一個數大於后一個數,則交換 if(d[j]<=d[j-1]){ temp = d[j]; d[j] = d[j-1]; d[j-1] = temp; flag = 1; } } //如果本次排序沒有進行一次交換,則break,減少了執行之間。 if(flag == 0){ break; } } }
運行結果如下: