批處理作業調度問題 ——回溯法

1、問題描述

每一個作業Ji都有兩項任務分別在2臺機器上完成。每個作業必須先有機器1處理,然後再由機器2處理。作業Ji需要機器j的處理時間爲tji。對於一個確定的作業調度,設Fji是作業i在機器j上完成處理時間。則所有作業在機器2上完成處理時間和f=F2i,稱爲該作業調度的完成時間和

2、簡單描述

  • 對於給定的n個作業,指定最佳作業調度方案,使其完成時間和達到最小。

區別於流水線調度問題:

  • 批處理作業調度旨在求出使其完成時間和達到最小的最佳調度序列;

  • 流水線調度問題旨在求出使其最後一個作業的完成時間最小的最佳調度序列;

3、題意理解

在這裏插入圖片描述 
考慮n=3 這個3個作業的6種可能的調度方案是(1,2,3)(1,3,2)(2,1,3)(2,3,1)(3,1,2)(3,2,1)對應的完成時間和分別是19,18,20,21,19,19。對於每一種調度,其調度時間圖的形式如下,下圖爲n=5的情況:
在這裏插入圖片描述

4、舉例說明

批處理作業調度問題要從n個作業的所有排列中找出有最小完成時間和的作業調度,所以批處理作業調度問題的解空間是一顆排列樹。

在這裏插入圖片描述
按照回溯法搜索排列樹的算法框架,設開始時x=[1,2, … , n]是所給的n個作業,則相應的排列樹由x[1:n]的所有排列(所有的調度序列)構成。

二維數組M是輸入作業的處理時間,bestf記錄當前最小完成時間和,bestx記錄相應的當前最佳作業調度。

在遞歸函數Backtrack中,

  • 當i>n時,算法搜索至葉子結點,得到一個新的作業調度方案。此時算法適時更新當前最優值和相應的當前最佳調度。
  • 當i<n時,當前擴展結點位於排列樹的第(i-1)層,此時算法選擇下一個要安排的作業,以深度優先方式遞歸的對相應的子樹進行搜索,對不滿足上界約束的結點,則剪去相應的子樹。

4、算法分析

  1. 區分作業i和當前第i個正在執行的作業
       給x賦初值,即其中一種排列,如x=[1,3,2];M[x[j]][i]代表當前作業調度x排列中的第j個作業在第i臺機器上的處理時間;如M[x[2]][1]就意味着作業3在機器1上的處理時間。

  2. bestf的初值
       此問題是得到最佳作業調度方案以便使其完成時間和達到最小,所以當前最優值bestf應該初始化賦值爲較大的一個值。

  3. f1、f2的定義與計算
       假定當前作業調度排列爲:x=[1,2,3];f1[i]即第i個作業在機器1上的處理時間,f2[j]即第j個作業在機器2上的處理時間;則:

     f1[1]=M[1][1] , f2[1]=f1[1]+M[1][2]
     f1[2]=f1[1]+M[2][1] , f2[2]=MAX(f2[1],f1[2])+M[2][2] //f2[2]不光要等作業2自己在機器1上的處理時間,還要等作業1在機器2上的處理時間,選其大者。
     f1[3]=f1[2]+M[3][1] , f2[3]=MAX(f2[2],f1[3])+M[3][2]
     1只有當前值有用,可以覆蓋賦值,所以定義爲int型變量即可,減少空間消耗;f2需要記錄每個作業的處理時間,所以定義爲int *型,以便計算得完成時間和。
    

4. f2[0]的初值
   f2[i]的計算都是基於上一個作業f2[i-1]進行的,所以要記得給f2[0]賦值爲0。

code

#include<iostream>
using namespace std;
int x[100];    //當前作業調度————其中一種排列順序
int bestx[100]; //當前最優作業調度
int m[100][100];////各作業所需的處理時間
                //M[j][i]代表第j個作業在第i臺機器上的處理時間
int f1=0;//機器1完成處理時間
int f2=0;//機器2完成處理時間
int cf=0;//完成時間和
int bestf=10000;//當前最優值,即最優的處理時間和
int n;//作業數
 
void swap(int &a,int &b)
{
    int temp=a;
    a=b;
    b=temp;
}
 
void Backtrack(int t)
{    //t用來指示到達的層數(第幾步,從0開始),同時也指示當前執行完第幾個任務/作業
    int tempf,j;
    if(t>n) //到達葉子結點,搜索到最底部
    {
        if(cf<bestf)
        {
            for(int i=1; i<=n; i++)
                bestx[i]=x[i];//更新最優調度序列
            bestf=cf;//更新最優目標值
        }
    }
    else    //非葉子結點
    {
        for(j=t; j<=n; j++) //j用來指示選擇了哪個任務/作業(也就是執行順序)
        {
            f1+=m[x[j]][1];//選擇第x[j]個任務在機器1上執行,作爲當前的任務
            tempf=f2;//保存上一個作業在機器2的完成時間
            f2=(f1>f2?f1:f2)+m[x[j]][2];//保存當前作業在機器2的完成時間
            cf+=f2;               //在機器2上的完成時間和
            //如果該作業處理完之後,總時間已經超過最優時間,就直接回溯。
            //剪枝函數
            if(cf<bestf) //總時間小於最優時間
            {
                swap(x[t],x[j]);  //交換兩個作業的位置,把選擇出的原來在x[j]位置上的任務調到當前執行的位置x[t]
                Backtrack(t+1);   //深度搜索解空間樹,進入下一層
                swap(x[t],x[j]); //進行回溯,還原,執行該層的下一個任務 //如果是葉子節點返回上一層
            }
            //回溯需要還原各個值
            f1-=m[x[j]][1];
            cf-=f2;
            f2=tempf;
        }
    }
}
 
int main()
{
    int i,j;
 
    cout<<"請輸入作業數:"<<endl;
    cin>>n;
 
    cout<<"請輸入在各機器上的處理時間"<<endl;
    for(i=1; i<=2; i++) //i從1開始
        for(j=1; j<=n; j++)
            cin>>m[j][i];//第j個作業,第i臺機器的時間值
    for(i=1; i<=n; i++)
        x[i]=i;//初始化當前作業調度的一種排列順序
    Backtrack(1);
 
    cout<<"調度作業順序:"<<endl;
    for(i=1; i<=n; i++)
        cout<<bestx[i]<<' ';
    cout<<endl;
 
    cout<<"處理時間:"<<endl;
    cout<<bestf;
    return 0;
}
/*
測試數據:
3
2 3 2
1 1 3
3
2 5 4
3 2 1
*/

參考自:
荷葉田田_

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章