動態規劃(dynamic programming)是通過組合子問題的解而解決整個問題的。分治演算法是指將問題劃分為一些獨立的子問題,遞迴地求解各子問題,然後合併子問題的解而得到原問題的解。動態規劃適用於子問題不是獨立的情況,也就是各子問題包含公共的子子問題。在這種情況下,若用分治法則會做許多不必要的工作,即重複地求解公共的子子問題。動態規劃對每個子子問題只求解一次,將結果儲存在一張表中,從而避免每次遇到各個子問題時重新計算答案。
動態規劃通常應用於最優化問題。此類問題可能有很多種可行解,每個解有一個值,我們希望找出一個具有最優(最大或最小)值的解,稱這樣的解為該問題的一個最優解(而不是確定的最優解),因為可能存在多個取最優解的值。
動態規劃演算法的設計可分為如下4個步驟:
(1)描述最優解的結構。
(2)遞迴定義最優解的值。
(3)按自底向上的方式計算最優解的值。
(4)由計算出的結果構造一個最優解。
裝配線排程
Colonel汽車公司在有兩條裝配線的工廠內生產汽車。一個汽車底盤在進入每一條裝配線後,在一些裝配站中會在底盤上安裝部件,然後,完成的汽車在裝配線的末端離開。每一條裝配線上有n個裝配站,編號為j=1,2,…,n。將裝配線i(i為1或2)的第j個裝配站表示為。裝配線1的第j個站()和裝配線2的第j個站()執行相同的功能。然而,這些裝配站是在不同的時間建造的,並且採用了不同的技術,因此,每個站上所需的時間是不同的,即使在兩天不同裝配線相同位置的裝配站上也是這樣。把在裝配站上所需的裝配時間記為。底盤進入裝配線i的進入時間為ei,裝配完的汽車離開裝配線i的離開時間為xi。
正常情況下,一旦一個底盤進入一條裝配線後,它只會經過該條裝配線。在相同的裝配線中,從一個裝配站到下一個裝配站所花的時間可以忽略。偶爾會來一個特別急的訂單,客戶要求儘可能快地製造這些汽車。對這些加急的訂單,底盤仍然依序經過n個裝配站,但工廠經理可以將部分完成的汽車在任何裝配站上從一條裝配線移到另一條裝配線上。把已經通過裝配站的一個底盤從裝配線i移走所花的時間為,其中i=1,2,j=1,2,…,n-1(因為在第n個裝配站後,裝配已經完成)。問題是要確定在裝配線1內選擇哪些站以及在裝配線2內選擇哪些站,以使汽車通過工廠的總時間最小。
解題報告:
步驟1:描述最優解的特徵。
對於裝配線排程問題,一個問題(找出通過裝配站的最快路線)最優解包含了子問題(找出通過或的最快路線)的一個最優解。稱這個性質為最優子結構,這是是否可以應用動態規劃方法的標誌之一。可以利用子問題的最優解來構造原問題的一個最優解。
步驟2:一個遞迴的解。
利用子問題的最優解來遞迴定義一個最優解的值。選擇在兩條裝配線上通過裝配站j的最快路線問題作為子問題,j=1,2,…,n。
令表示一個底盤從起點到裝配站的最快可能時間,為底盤通過工廠的所有路線的最快時間,則,其中,。進一步得出:
步驟3:計算最快時間。
整個過程花費時間。
步驟4:構造通過工廠的最快路線。
參考程式碼如下:
1 #include <iostream> 2 using namespace std; 3 4 typedef struct 5 { 6 int **a; 7 int **t; 8 int *e; 9 int *x; 10 int n; 11 }LineCondition; 12 13 void FastestWay(const LineCondition *line, int **&l, int **&f, int &ftotal, int &lend) 14 { 15 int n = line->n; 16 17 f[0][0] = line->e[0] + line->a[0][0]; 18 f[1][0] = line->e[1] + line->a[1][0]; 19 20 for (int i = 1; i < n; ++i) 21 { 22 if (f[0][i - 1] + line->a[0][i] <= f[1][i - 1] + line->t[1][i - 1] + line->a[0][i]) 23 { 24 f[0][i] = f[0][i - 1] + line->a[0][i]; 25 l[0][i] = 1; 26 } 27 else 28 { 29 f[0][i] = f[1][i - 1] + line->t[1][i - 1] + line->a[0][i]; 30 l[0][i] = 2; 31 } 32 33 if (f[1][i - 1] + line->a[1][i] <= f[0][i - 1] + line->t[0][i - 1] + line->a[1][i]) 34 { 35 f[1][i] = f[1][i - 1] + line->a[1][i]; 36 l[1][i] = 2; 37 } 38 else 39 { 40 f[1][i] = f[0][i - 1] + line->t[0][i - 1] + line->a[1][i]; 41 l[1][i] = 1; 42 } 43 } 44 45 if (f[0][n - 1] + line->x[0] <= f[1][n - 1] + line->x[1]) 46 { 47 ftotal = f[0][n - 1] + line->x[0]; 48 lend = 1; 49 } 50 else 51 { 52 ftotal = f[1][n - 1] + line->x[1]; 53 lend = 2; 54 } 55 } 56 57 void PrintStations(int **l, int lend, int n) 58 { 59 int *a = new int[n]; 60 int i = lend - 1; 61 int m = n; 62 a[--m] = i + 1; 63 64 for (int j = n - 1; j > 0; --j) 65 { 66 i = l[i][j] - 1; 67 a[--m] = i + 1; 68 } 69 70 for (int i = 0; i < n; ++i) 71 { 72 cout << "line " << a[i] << ", station " << i + 1 << endl; 73 } 74 75 delete [] a; 76 } 77 78 79 int main(void) 80 { 81 LineCondition line; 82 83 cout << "請輸入裝配站數目n:"; 84 cin >> line.n; 85 86 line.a = new int *[2]; 87 for (int i = 0; i < 2; ++i) 88 { 89 line.a[i] = new int [line.n]; 90 } 91 for (int i = 0; i < 2; ++i) 92 { 93 cout << "請輸入裝配線" << i + 1 << "上每個裝配站耗費時間a[i][j]:"; 94 for (int j = 0; j < line.n; ++j) 95 { 96 cin >> line.a[i][j]; 97 } 98 } 99 100 line.t = new int *[2]; 101 for (int i = 0; i < 2; ++i) 102 { 103 line.t[i] = new int [line.n - 1]; 104 } 105 for (int i = 0; i < 2; ++i) 106 { 107 cout << "請輸入裝配線t[" << i << "][j]:"; 108 for (int j = 0; j < line.n - 1; ++j) 109 { 110 cin >> line.t[i][j]; 111 } 112 } 113 114 line.e = new int [2]; 115 cout << "請輸入e1,e2:"; 116 cin >> line.e[0] >> line.e[1]; 117 118 line.x = new int [2]; 119 cout << "請輸入x1,x2:"; 120 cin >> line.x[0] >> line.x[1]; 121 122 int **l = new int *[2]; 123 int **f = new int *[2]; 124 for (int i = 0; i < 2; ++i) 125 { 126 l[i] = new int [line.n]; 127 f[i] = new int [line.n]; 128 } 129 130 int lend, ftotal; 131 FastestWay(&line, l, f, ftotal, lend); 132 PrintStations(l, lend, line.n); 133 134 return 0; 135 }
按照如下圖片輸入相關資料:
測試結果如下: