1. 程式人生 > >[C++] 動態規劃之矩陣連乘、最長公共子序列、最大子段和、最長單調遞增子序列

[C++] 動態規劃之矩陣連乘、最長公共子序列、最大子段和、最長單調遞增子序列

每次 種子 () return 避免 amp 可能 text com

一、動態規劃的基本思想

  動態規劃算法通常用於求解具有某種最優性質的問題。在這類問題中,可能會有許多可行解。每一個解都對應於一個值,我們希望找到具有最優值的解。

  將待求解問題分解成若幹個子問題,先求解子問題,然後從這些子問題的解得到原問題的解。適合於用動態規劃求解的問題,經分解得到子問題往往不是互相獨立的。若用分治法來解這類問題,則分解得到的子問題數目太多,有些子問題被重復計算了很多次。如果我們能夠保存已解決的子問題的答案,而在需要時再找出已求得的答案,這樣就可以避免大量的重復計算,節省時間。為了達到此目的,我們可以用一個表來記錄所有已解的子問題的答案。不管該子問題以後是否被用到,只要它被計算過,就將其結果填入表中。

二、動態規劃的基本要素(特征)

  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++] 動態規劃之矩陣連乘、最長公共子序列、最大子段和、最長單調遞增子序列