動態規劃之 裝配線調度問題

從之前提到的最長公共子序列的問題中已經可以看到動態規劃的應用之處,但是對於這種算法,或者說是一種思想,該在什麼地方使用,哪些問題的解決可以使用動態規劃,可能並不清晰。下文所講述的內容就是可用動態規劃解決問題的兩個要素:最優子結構和重疊子問題。

在分析這兩個要素之前,先以兩個例子引入:

裝配線調度

假設一個汽車底盤加工有兩個裝配線,如下如所示,每個裝配線都有n個配件站,用於給底盤安裝不同的零件,配件站用S表示,如S(2,3)表示第二條裝配線的第三個配件站。兩條裝配線同一個配件站的工作相同,但是時間不同,用a表示所花費的時間,如a(2,3)表示裝配站2的第三個配件站完成工作的時間。底盤進入裝配線的時間用e表示,離開裝配線的時間用x表示。


一般情況下,一個汽車底盤的裝配過程就是進入一條裝配線然後依次通過配件展,一條裝配線內從一個配件站到另一個的時間可以忽略。但是某些時候可能會需要一些加急的訂單,對這些訂單仍舊需要經過所有的配件站,但不能在一條裝配線上一直運行,這樣的最短時間就無法保證。解決方法就是從一條裝配線的配件站轉移到另一條裝配線的配件站,但轉移的過程需要時間,記爲t如上圖,問題就是要尋找需要在哪些配件站進行轉移達到時間的最小化。在下圖中所示例子可以得到一種最優轉移方案:


或許蠻力求解是一種解決方案,但當配件站的數目一旦增多,時間複雜度是按照指數的形式增長的,所以和最長公共子序列一樣,窮舉是不可能的。按照LCS問題一樣,給出4個步驟:

步驟一:通過工廠最快路線的結構

第一步其實就是要描述最優解結構的特徵,假設底盤到達配件站S(1,j)的路徑是最短的,那麼到達這個配件站的前一個配件站可以是S(1,j-1)或者是S(2,j-1),當然,到達S(1,j-1)或者S(2,j-1)的路徑同樣是最短的。到達S(1,j)的最優解包含了到達S(1,j-1)或者是S(2,j-1)的最優解,我們稱這個性質爲最優子結構,這也是能否使用動態規劃的一個標誌。

下面就是利用最優子結構來進行說明,用子問題的一個最優解來構造原問題的最優解。通過配件站S(1,j)的最快路線,必然裝配線1或者2的j-1的配件站,因此,通過S(1,j)的最快路線只能是以下兩種選擇:

·通過配件站S(1,j-1),然後直接到達S(1,j)。

·通過配件站S(2,j-1),然後從裝配線2到裝配線1,最後到達S(1,j)。

當然,對於配件站S(2,j)的路徑是對稱的。所以,要解決到達某一個配件站的最短時間,就要解決兩個字問題的最短時間問題,而通過對子問題的求解就可以構造出問題的最優解。

步驟二:一個遞歸的解

在動態規劃的第二步中,就是通過對子問題的最優解來遞歸定義一個最優解,在裝配線問題中,我們就選擇達到配件站j的最快路線的問題作爲子問題,令fi[j]表示底盤從起點到達配件站S(i,j)的最快時間,而我們的目的就是確定底盤到達最後出口的最短時間,記爲f*,每條裝配線的配件站的數目都是n,於是有:


對於f(1,1)和f(2,1)的推導也是很容易:


現在的問題就是該如何計算f(i,j),而f(i,j)就是之前所述的直接通過同一裝配線的上一個配件站或者通過不同裝配線然後到達S(i,j),於是就有:


f(i,j)的值就是子問題最優解的值,爲了有助於跟蹤最優解的構造過程,定義l(i,j)爲裝配線的編號(1或2),其中的配件站j-1被通過配件站S(i,j)的的最快路徑所使用。舉個例子吧,就例如上圖,l(1,6)的值就是2,表示向後應該找裝配線2上的配件站了,對應於S(2,5),而l(2,5)=2,那麼下一個對應於配件站S(2,4)。

步驟三:計算最快時間

通過前面得到的遞歸可以很快算出來,下面給出僞代碼:

   

在這段代碼中,顯示直接可以計算出f(1,1)和f(2,1)的值,然後通過遞歸式子算出每一個f(1,j)和f(2,j)的大小,用l變量保存每個f(i,j)之前的那個配件站是從哪條裝配線過來的,最後通過x變量求出最短的時間f*。

步驟四:構造通過工廠的最快路線

由於變量l已經記錄好了之前的那一個配件站的位置,所以只要一路回溯過去就行了


程序的源代碼如下:

#include <stdio.h>
#include <stdlib.h>

/*
下面的公共數據是對配件站的模擬,f_star對應於文章中的f*,l_star對應於l*
其餘路徑均一數組形式存放,所有的數據都是從1開始計算,所以數組第0號位置全部存放0
*/

int f_star,l_star;
int f[2+1][6+1];
int l[2+1][6+1];
int a[2+1][6+1]={{0,0,0,0,0,0,0},{0,7,9,3,4,8,4},{0,8,5,6,4,5,7}};
int t[2+1][5+1]={{0,0,0,0,0,0},{0,2,3,1,3,4},{0,2,1,2,2,1}};
int e[2+1]={0,2,4};
int x[2+1]={0,3,2};

void FASTEST_WAY()
{
    int j;
    f[1][1]=e[1]+a[1][1];f[2][1]=e[2]+a[2][1];
    for(j=2;j<=6;j++)
    {
        if(f[1][j-1]+a[1][j]<=f[2][j-1]+t[2][j-1]+a[1][j])
        {
            f[1][j]=f[1][j-1]+a[1][j];
            l[1][j]=1;
        }
        else
        {
            f[1][j]=f[2][j-1]+t[2][j-1]+a[1][j];
            l[1][j]=2;
        }
        if(f[2][j-1]+a[2][j]<=f[1][j-1]+t[1][j-1]+a[2][j])
        {
            f[2][j]=f[2][j-1]+a[2][j];
            l[2][j]=2;
        }
        else
        {
            f[2][j]=f[1][j-1]+t[1][j-1]+a[2][j];
            l[2][j]=1;
        }
    }
    if(f[1][6]+x[1]<=f[2][6]+x[2])
    {
        f_star=f[1][6]+x[1];
        l_star=1;
    }
    else
    {
        f_star=f[2][6]+x[2];
        l_star=2;
    }
}

void PRINT_STATIONS()
{
    int j;
    int i=l_star;
    printf("line %d,station %d\n",i,6);
    for(j=6;j>=2;j--)
    {
        i=l[i][j];
        printf("line %d,station %d\n",i,j-1);
    }
}

int main()
{
    FASTEST_WAY();
    printf("最短路線f_star=%d\n",f_star);
    printf("以下是每個配件站的位置:\n");
    PRINT_STATIONS();
    return 0;
}



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