問題描述
題目分析
這道題目也是一個比較經典的回溯法問題,但是這個問題和我們之前說的裝載問題
不同的是,這裏面的每一種作業我們都要安排,而不是找其中的子集,而裝載問題纔是一個找子集的問題。所以根據回溯法框架裏的內容,對於這種解空間中必須包含全部而不是找子集的情況,我們應該使用的解空間是排列樹。結構如下圖所示:
我們可以將這些序列看成是在x=(1,2,3)的基礎上經過各種不同作業的兩兩交換而得到的不同調度方案。在回溯法的框架中我們也提到了,解空間爲排列樹的問題是通過不斷交換來進行選擇不同情況的,在回溯的時候還需要將二者交換回來。
在這裏面我們需要聲明一個f2的一維數組表示機器2完成的時間,f1表示機器1完成的時間,M是一個二維數組,表示所有作業的調度時間。f2之所以用數組表示,是因爲我們其實在過程中f2存儲的是機器1完成的時間+機器2完成的時間(因爲機器1完成之後機器2才能完成,所以可以統一存儲)。但是後一項作業的結束時間取決於上一項作業的結束時間,如果上一項還沒完成,則機器2一定被佔用着,當前工作也就不能完成,這都實現中的一些細節,等下我們會看到具體如何展開。可以看代碼中的註釋.
代碼
#include <iostream>
using namespace std;
class Flowshop()
{
//參數是作業調度時間表,作業數和當前的做優調度
friend Flow(int **,int,int []);
private:
void Backtrack(int i);//參數是現在要執行的作業次序,不是具體的哪個作業
int **M,//各作業所需的處理時間
int *x,//作業的調度
int *bestx,//當前的最優作業調度
int *f2,//機器2完成處理時間,這裏之所以聲明爲數組是因爲f2和i有關係,我們會記錄下某一工作結束後的完成時間儲存在f2[i]裏
int f1,//機器1完成處理時間
int f,//完成時間和
int bestf,//當前最優值,
int n;//作業數
};
void Flowshop::Backtrack(int i)
{
if(i > n)
//到達了葉節點,我們需要更新當前最優調度和最優值
{
for(int j = 1;j < n;j++)
{
bestx[j] = x[j];
}
bestf = f;
}
else
{
//這裏的重點是這個j
//j表示的是具體的工作,而i是作業分量,即我們現在正在要做第i個作業,於是我們選擇作業j作爲第i個要進行的作業
for(int j = i;j < n;j++)
{
//機器1完成時間我們只需要簡單進行加和
f1 += M[x[j]][1];
//機器2完成時間取決於上一個工作的完成時間,i-1不完成我們無法完成i
f2[i] = ((f2[i-1] > f1) ? f2[i-1] : f1) + M[x[j]][2];
f += f2[i];
if(f < bestf)
{
swap(x[i],x[j]);
Backtrack(i + 1);
swap(x[i],x[j]);
}
f1 -= M[x[j]][1];
f -= f2[i];
}
}
}
int Flow(int **M,int n,int bestx[])
{
int ub = INT_MAX;
Flowshop X;
X.x = new int [n + 1];
X.f2 = new int [n + 1];
X.M = M;
X.n = n;
X.bestx = bestx;
X.bestf = ub;
X.f1 = 0;
X.f = 0;
for(int i = 0;i <= n;i++)
{
X.f2[i] = 0;
X.x[i] = i;
}
X.Backtrack(1);
delete[] X.x;
delete[] X.f2;
return X.bestf;
}
總結
由於這是一個排列樹解空間,所以我們的時間複雜應該爲O(n!)
(排列組合問題)