[C++] 動態規劃之矩陣連乘、最長公共子序列、最大子段和、最長單調遞增子序列
一、動態規劃的基本思想
動態規劃算法通常用於求解具有某種最優性質的問題。在這類問題中,可能會有許多可行解。每一個解都對應於一個值,我們希望找到具有最優值的解。
將待求解問題分解成若幹個子問題,先求解子問題,然後從這些子問題的解得到原問題的解。適合於用動態規劃求解的問題,經分解得到子問題往往不是互相獨立的。若用分治法來解這類問題,則分解得到的子問題數目太多,有些子問題被重復計算了很多次。如果我們能夠保存已解決的子問題的答案,而在需要時再找出已求得的答案,這樣就可以避免大量的重復計算,節省時間。為了達到此目的,我們可以用一個表來記錄所有已解的子問題的答案。不管該子問題以後是否被用到,只要它被計算過,就將其結果填入表中。
二、動態規劃的基本要素(特征)
1、最優子結構:
當問題的最優解包含了其子問題的最優解時,稱該問題具有最優子結構性質。
2、重疊子問題:
在用遞歸算法自頂向下解問題時,每次產生的子問題並不總是新問題,有些子問題被反復計算多次。動態規劃算法正是利用了這種子問題的重疊性質,對每一個子問題只解一次,而後將其解保存在一個表格中,在以後盡可能多地利用這些子問題的解。
三、用動態規劃求解問題的主要步驟
1、找出最優解的性質,並刻畫其結構特征;
2、遞歸地定義最優值(寫出動態規劃方程);
3、以自底向上的方式計算出最優值;
4、根據計算最優值時得到的信息,構造一個最優解。
說明:(1)步驟 1~3 是動態規劃算法的基本步驟;
(2)在只需要求出最優值的情形,步驟 4 可以省略;
(3)若需要求出問題的一個最優解,則必須執行步驟 4。
四、動態規劃實例
1、矩陣連乘問題
m × n 矩陣 A 與 n × p 矩陣 B 相乘需耗費的時間。我們把 m x n x p 作為兩個矩陣相乘所需時間的測量值。
現在假定要計算三個矩陣 A、B 和 C 的乘積,有兩種方式計算此乘積。
(1)先用 A 乘以 B 得到矩陣 D,然後 D 乘以 C 得到最終結果,這種乘法的順序為(AB)C;
(2)先用 B 乘以 C 得到矩陣 E,然後 E 乘以 A 得到最終結果,這種乘法的順序為 A(BC)。
盡管這兩種不同的計算順序所得的結果相同,但時間消耗會有很大的差距。
實例:
圖1.1 A、B 和 C 矩陣
矩陣乘法符合結合律,所以在計算 ABC 矩陣連乘時,有兩種方案,即 (AB)C 和 A(BC)。
對於第一方案(AB)C 和,計算:
圖1.2 AB 矩陣相乘
其乘法運算次數為:2 × 3 × 2 = 12
圖1.3 (AB)C 矩陣連乘
其乘法運算次數為:2 × 2 × 4 = 16
總計算量為:12 + 16 = 28
對第二方案 A(BC),計算:
圖1.4 BC 矩陣相乘
其乘法運算次數為:3 × 2 × 4 = 24
圖1.5 A、B 和 C 矩陣連乘
其乘法運算次數為:2 × 3 × 4 = 24
總計算量為:24 + 24 = 48
可見,不同方案的乘法運算量可能相差很懸殊。
問題定義:
給定 n 個矩陣 {A1, A2, …, An},其中 Ai 與 Ai+1 是可乘的,i = 1,2,…,n-1。考察這 n 個矩陣的連乘積 A1A2…An。 由於矩陣乘法滿足結合律,所以計算矩陣的連乘可以有許多不同的計算次序。
這種計算次序可以用加括號的方式來確定。完全加括號的矩陣連乘積可遞歸地定義為:
(1)單個矩陣是完全加括號的;
(2)矩陣連乘積 A 是完全加括號的,則 A 可表示為 2 個完全加括號的矩陣連乘積 B 和 C 的乘積並加括號,即 A = (BC)。設有四個矩陣 A, B, C, D,總共有五種完全加括號的方式: (A((BC)D)) , (A(B(CD))) , ((AB)(CD)) , (((AB)C)D) , ((A(BC)D))。
a. 找出最優解的性質,並刻畫其結構特征;
將矩陣連乘積 AiAi+1…Aj ,簡記為 A[i : j], 這裏 i≤j;考察計算 A[1:n] 的最優計算次序。
設這個計算次序在矩陣 Ak 和 Ak+1 之間將矩陣鏈斷開,1 ≤ k < n,則其相應完全加括號方式為 (A1A2…Ak)(Ak+1Ak+2…An)。
總計算量 = A[1:k] 的計算量 + A[k+1:n]的計算量 + A[1:k]和A[k+1:n]相乘的計算量
特征:計算 A[1:n] 的最優次序所包含的計算矩陣子鏈 A[1:k] 和 A[k+1:n] 的次序也是最優的。
b. 遞歸地定義最優值(寫出動態規劃方程);
圖1.6 建立遞歸關系
c. 以自底向上的方式計算出最優值。
1 #include <iostream> 2 using namespace std; 3 4 #define NUM 51 5 int p[NUM]; //矩陣維數 P0 x P1,P1 x P2,P2 x P3,...,P5 x P6 6 int m[NUM][NUM]; //最少乘次數 / 最優值數組 7 int s[NUM][NUM]; //最優斷開位置 8 9 void MatrixChain(int n) 10 { 11 for (int i = 1; i <= n; i++) m[i][i] = 0; 12 13 for (int r = 2; r <= n; r++) //矩陣個數 14 for (int i = 1; i <= n - r+1; i++) //起始 15 { 16 int j=i+r-1; //結尾 17 m[i][j] = m[i+1][j]+ p[i-1]*p[i]*p[j]; //計算初值,從i處斷開,計算最優斷開位置 18 s[i][j] = i; 19 for (int k = i+1; k < j; k++) 20 { 21 int t = m[i][k] + m[k+1][j] + p[i-1]*p[k]*p[j]; 22 if (t < m[i][j]) { m[i][j] = t; s[i][j] = k;} 23 } 24 } 25 } 26 27 void TraceBack(int i, int j) 28 { 29 if(i==j) 30 cout<<"A"<<i; 31 else 32 { 33 cout<<"("; 34 TraceBack(i,s[i][j]); 35 TraceBack(s[i][j]+1,j); 36 cout<<")"; 37 } 38 } 39 40 int main() 41 { 42 int n; 43 scanf("%d", &n); 44 int i, temp; 45 for (i=0; i<n; i++) 46 scanf("%d%d", &p[i], &temp); 47 p[n] = temp; 48 MatrixChain(n); 49 printf("%d\n", m[1][n]); 50 TraceBack(1, n); 51 return 0; 52 }
[C++] 動態規劃之矩陣連乘、最長公共子序列、最大子段和、最長單調遞增子序列