算法導論一書的第四部分—高級設計和分析技術從本章開始討論,主要分析高效算法的三種重要技術:動態規劃、貪心算法以及平攤分析三種。
首先,本章討論動態規劃,它是經過組合子問題的解而解決整個問題的,一般應用於最優化問題。
動態規劃算法的設計能夠分爲以下4個步驟:ios
第一個動態規劃的例子是求解一個製造問題,Colonel汽車公司在有兩條裝配線的工廠生產汽車,具體以下圖所示:
針對裝配線調度問題,下面給出一個實例,要求利用動態規劃方法分析設計程序實現。
算法
/* * 《算法導論》第十五章 動態規劃 * 15.1 裝配線調度問題 */
#include <iostream>
#include <cstdlib>
using namespace std;
//初始化裝配線以及站點個數
const int L = 2;
const int S = 6;
//程序輸入:裝配點耗費時間、底盤進入耗費時間、裝配點移動耗費時間、底盤離開耗費時間
int a[L + 1][S + 1] , e[L + 1], t[L + 1][S], x[L + 1] , n = S;
//程序輸出:
int f[L + 1][S + 1], l[L + 1][S + 1] , rf , rl;
//求解f[][] 用到的參數int **a, int *e, int **t, int *x, int n
void FastestWay()
{
f[1][1] = e[1] + a[1][1];
f[2][1] = e[2] + a[2][1];
//由公式15.4 - 15.7 推導計算
for (int j = 2; j <= n; 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][n] + x[1] <= f[2][n] + x[2])
{
rf = f[1][n] + x[1];
rl = 1;
}
else{
rf = f[2][n] + x[2];
rl = 2;
}
}
//打印調度結果
void PrintStations()
{
//最後一個站點的裝配線號
int i = rl;
cout << "倒序結果輸出:" << endl;
cout << "line : " << i << " , station : " << n << endl;
for (int j = n; j >= 2; j--)
{
i = l[i][j];
cout << "line : " << i << " , station : " << j - 1 << endl;
}
}
//打印調度結果 —— 以站號遞增的順序輸出裝配站
void PrintStations2()
{
int r[S] , tem = rl;
for (int j = S; j > 1 ; j--)
{
tem= l[tem][j];
r[j - 1] = tem;
}
cout << "正序結果輸出:" << endl;
for (int j = 1; j < S; j++)
{
cout << "line : " << r[j] << " , station : " << j << endl;
}
//輸出最後一個裝配站
cout << "line : " << rl << " , station : " << n << endl;
}
void Input()
{
cout << "請輸入裝配點耗費時間:" << endl;
for (int i = 1; i <= L; i++)
for (int j = 1; j <= S; j++)
{
cin >> a[i][j];
}
cout << "請輸入進入裝配點耗費時間:" << endl;
for (int i = 1; i <= L; i++)
{
cin >> e[i];
}
cout << "請輸入站點移動耗費時間:" << endl;
for (int i = 1; i <= L; i++)
for (int j = 1; j < S; j++)
{
cin >> t[i][j];
}
cout << "請輸入離開裝配點耗費時間:" << endl;
for (int i = 1; i <= L; i++)
{
cin >> x[i];
}
}
void Output()
{
int i, j;
cout << "輸出f[i][j]" << endl;
//f[i][j]表示第j個站是在裝配線i上完成的,完成1到j的裝配最少須要的時間
for (i = 1; i <= L; i++)
{
for (j = 1; j <= S; j++)
cout << f[i][j] << ' ';
cout << endl;
}
cout << "rf = " << rf << endl;
cout << "輸出l[i][j]" << endl;
//l[i][j]表示使得f[i][j]最小時在哪一個裝配線上裝配j-1
for (i = 1; i <= L; i++)
{
for (j = 2; j <= S; j++)
cout << l[i][j] << ' ';
cout << endl;
}
cout << "rl = " << rl << endl;
}
//主程序
int main()
{
//數據輸入
Input();
FastestWay();
//數據輸出
Output();
//結果打印
PrintStations();
PrintStations2();
system("pause");
return 0;
}
//遞歸輸出結果,參數爲:(站點,裝配線編號)
void PrintStations3(int s, int ll)
{
cout << "正序結果輸出:" << endl;
if (s < n)
ll = l[ll][s + 1];
if (s > 1)
PrintStations3(s - 1, ll);
cout << "line : " << ll << " , station : " << s << endl;
}
若Canty教授猜想正確,存在知足
markdown
解決矩陣鏈乘問題是動態規劃的一個典型實例。
優化
/* * 《算法導論》第十五章 動態規劃 * 矩陣鏈乘法 */
#include <iostream>
#include <cstdlib>
using namespace std;
const int N = 6; //進行鏈乘的矩陣數目
//鏈乘矩陣維數序列
int A[N + 1] = { 30, 35, 15, 5, 10, 20, 25 };
//int A[N + 1] = { 5, 10, 3, 12, 5, 50, 6 };
//動態優化結果存儲
int m[N + 1][N + 1] , s[N+1][N+1];
void MatrixChainOrder()
{
int i, j, k , l, q;
for (i = 1; i <= N; i++)
m[i][i] = 0;
for (l = 2; l <= N; l++)
{
for (i = 1; i <= N - l + 1; i++)
{
j = i + l - 1;
m[i][j] = 0x7fffffff;
for (k = i; k <= j - 1; k++)
{
q = m[i][k] + m[k + 1][j] + A[i - 1] * A[k] * A[j];
if (q < m[i][j])
{
m[i][j] = q;
s[i][j] = k;
}
}
}//for
}
}
void PrintOptimalParens(int i , int j)
{
if (i == j)
cout << "A" << i << " ";
else{
cout << "(";
PrintOptimalParens( i, s[i][j]);
PrintOptimalParens( s[i][j] + 1, j);
cout << ")";
}
}
int main()
{
MatrixChainOrder();
PrintOptimalParens(1, N);
cout << endl;
system("pause");
return 0;
}
最優加括號結果爲:
ui
採用動態規劃方法的最優化問題中的兩個要素:最優子結構和重疊子問題。spa
2.重疊子問題
適用於動態規劃求解的最優化問題必須具備的第二個要素是子問題的空間要「很小」,也就是用來解原問題的遞歸算法能夠反覆的解一樣的子問題,而不是總在產生新的子問題。設計
/* * 《算法導論》第十五章 動態規劃 * 15.3 動態規劃基礎 * 重疊子問題實現 */
#include <iostream>
#include <cstdlib>
using namespace std;
const int N = 6; //進行鏈乘的矩陣數目
//動態優化結果存儲
int m[N + 1][N + 1], s[N + 1][N + 1];
int RecusiveMatrixChain(int *A, int i, int j)
{
if (i == j)
return 0;
m[i][j] = 0x7fffffff;
for (int k = i; k <= j - 1; k++)
{
int q = RecusiveMatrixChain(A, i, k) + RecusiveMatrixChain(A, k + 1, j) + A[i - 1] * A[k] * A[j];
if (q < m[i][j])
{
m[i][j] = q;
s[i][j] = k;
}
}//for
return m[i][j];
}
void PrintOptimalParens(int i, int j)
{
if (i == j)
cout << "A" << i << " ";
else{
cout << "(";
PrintOptimalParens(i, s[i][j]);
PrintOptimalParens(s[i][j] + 1, j);
cout << ")";
}
}
int main()
{
//鏈乘矩陣維數序列
int A[N + 1] = { 30, 35, 15, 5, 10, 20, 25 };
//int A[N + 1] = { 5, 10, 3, 12, 5, 50, 6 };
RecusiveMatrixChain(A, 1, N);
PrintOptimalParens(1, N);
cout << endl;
system("pause");
return 0;
}
動態規劃有一種變形,它既具備一般的動態規劃方法的效率,又採用了一種自頂向下的策略。其思想就是備忘原問題的天然但低效的遞歸算法。code
/* * 《算法導論》第十五章 動態規劃 * 15.3 動態規劃基礎 * 作備忘錄 */
#include <iostream>
#include <cstdlib>
using namespace std;
const int N = 6; //進行鏈乘的矩陣數目
//動態優化結果存儲
int m[N + 1][N + 1], s[N + 1][N + 1];
int LookUpChain(int *A, int i, int j)
{
if (m[i][j] < 0x7fffffff)
return m[i][j];
if (i == j)
m[i][j] = 0;
else
{
for (int k = i; k <= j - 1; k++)
{
int q = LookUpChain(A, i, k) + LookUpChain(A, k + 1, j) + A[i - 1] * A[k] * A[j];
if (q < m[i][j])
{
m[i][j] = q;
s[i][j] = k;
}
}//for
}
return m[i][j];
}
int MemorizedMatrixChain(int *A)
{
for (int i = 1; i <= N; i++)
for (int j = 1; j <= N; j++)
{
m[i][j] = 0x7fffffff;
}
return LookUpChain(A, 1, N);
}
void PrintOptimalParens(int i, int j)
{
if (i == j)
cout << "A" << i << " ";
else{
cout << "(";
PrintOptimalParens(i, s[i][j]);
PrintOptimalParens(s[i][j] + 1, j);
cout << ")";
}
}
int main()
{
//鏈乘矩陣維數序列
int A[N + 1] = { 30, 35, 15, 5, 10, 20, 25 };
//int A[N + 1] = { 5, 10, 3, 12, 5, 50, 6 };
MemorizedMatrixChain(A);
PrintOptimalParens(1, N);
cout << endl;
system("pause");
return 0;
}