動態規劃引入—矩陣乘法

                    

 動態規劃算法適用於解最優化問題。一般可按如下4個步驟進行:算法

  1.找出最優解的性質,並刻畫其結構特徵數組

  2.遞歸地定義最優質優化

  3.以自底向上的方式計算出最優值spa

  4.根據計算最優值時獲得的信息,構造最優解.net

 

舉例:矩陣乘法問題code

 以兩個矩陣相乘爲例,A1*A2,A1和A2爲兩個矩陣,假設A1的行列數是p*q,A2的行列數是q*r。注意這裏因爲是A1乘以A2,因此A1的列數要等於A2的行數,不然沒法作矩陣乘法,知足上述條件的矩陣,咱們稱之爲「相容」的。那麼對於A1*A2而言,咱們須要分別執行p*r次對應A1的行元素乘以A2的列元素,根據線性代數知識,不可貴出咱們一共須要執行p*q*r次乘法。blog

 

        對於兩個矩陣相乘,一旦矩陣的大小肯定下來了,那麼所需執行的乘法次數就肯定下來了。那麼對於兩個以上的矩陣呢?是否是也是這樣呢。實際上,對於多個矩陣相乘,乘法執行的次數與「劃分」有關。例如:遞歸

 

        以矩陣鏈<A1,A2,A3>爲例,假設三個矩陣的規模分別爲10X100,100X5和5X50。class

 

        ①以((A1*A2)*A3)方式劃分,乘法執行次數爲:10*100*5+10*5*50=5000+2500=7500次循環

 

        ②以(A1*(A2*A3))方式劃分,乘法執行次數爲:100*5*50+10*100*50=25000+50000=75000次

 

        咱們能夠發現,對於一樣的矩陣鏈<A1,A2,A3>相乘而言,不一樣的劃分,乘法次數竟然相差10倍。

2、如何得到最佳的矩陣鏈乘法劃分和最少次數

 

        這裏咱們須要兩個二維數組記錄記錄是從哪裏「斷開」(s),記錄"每一段"到哪裏截止(m)。以下圖:

 

 

 

 使用一個長度爲n+1的一維數組p來記錄每一個矩陣的規模,其中n爲矩陣下標i的範圍1~n,例如對於矩陣Ai而言,它的規模應該是p[i-1]到p[i]。因爲i是從1到n取值,因此數組p的下標是從0到n。

 

       用於存儲最少乘法執行次數和最佳分段方式的結構是兩個二維數組m和s,都是從1~n取值。m[i][j]記錄矩陣鏈<Ai,Ai+1,...,Aj>的最少乘法執行次數,而s[i][j]則記錄 最優質m[i][j]的分割點k。

 

       須要注意的一點是當i=j時,m[i][j]=m[i][i]=0,由於一個矩陣不須要任何乘法。

 

       假設矩陣鏈從Ai到Aj,有j-i+1個矩陣,咱們從k處分開,將矩陣鏈分爲Ai~Ak和Ak+1到Aj兩塊,那麼咱們能夠比較容易的給出m[i][j]從k處分隔的公式:

 

       m[i][j]=m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j];

 

       在一組肯定的i和j值的狀況下,要使m[i][j]的值最小,咱們只要在全部的k取值中,i<=k<j,尋找一個讓m[i][j]最小的值便可。

 

       假設L爲矩陣鏈的長度,那麼L=j-i+1。當L=1時,只有一個矩陣,不須要計算。那麼咱們能夠從L=2到n進行循環,對每一個合理的i和j值的組合,遍歷全部k值對應的m[i][j]值,將最小的一個記錄下來,存儲到m[i][j]中,並將對應的k存儲到s[i][j]中,就獲得了咱們想要的結果。

   根據上面的分析,不難給出過程的代碼,注意這裏使用的自底向上的方法:

 1 //Ai矩陣的行列分別是p[i-1]和p[i],1<=i<=n  
 2   
 3 /* 
 4  * 求解最少次數的乘法括號劃分方案 
 5  */  
 6 void Matrix_Chain(int* p, int n, int** m, int** s) {  
 7   
 8     //①將對角線上的值先賦值爲0  
 9     for (int i = 1; i <= n; i++) {  
10         m[i][i] = 0;  
11     }  
12   
13     int l = 0; //l爲矩陣鏈的長度  
14   
15     //m[i][j]的第一個參數  
16         int i = 0;  
17   
18     //m[i][j]的第二個參數  
19     int j = 0;  
20   
21   
22   
23     int tmp = 0;  
24   
25     //②以長度L爲劃分,L從2開始到n  
26     for (l = 2; l <= n; l++) {  
27   
28         //循環第一個參數,由於l的長度至少爲2,因此i和j在這個循環裏面確定不相等  
29         for (i = 1; i <= n - l + 1; i++) {  
30             //由於j-i+1=l,因此j=l+i-1  
31             j = i + l - 1;  
32   
33             //給m[i][j]賦初值,這裏要尋找m[i][j]的最小值,原本應當給m[i][j]賦值一個正無窮,可是這裏直接賦一個i=j時候的特值也能夠  
34             m[i][j] = m[i][i] + m[i + 1][j] + p[i - 1] * p[i] * p[j];  
35             s[i][j] = i;  
36   
37             //對於每一個特定的i和j的組合,遍歷此時全部的合適k值,k大於等於i小於j  
38             for (int k = i + 1; k < j; k++) { //這裏k不能等於j,由於後面要m[k+1][j],否則k+1就比j大了  
39   
40                 tmp = m[i][k] + m[k + 1][j] + p[i - 1] * p[k] * p[j];  
41   
42                 if (tmp < m[i][j]) {  
43                     m[i][j] = tmp;  
44                     s[i][j] = k;  
45                 }  
46             }  
47         }  
48     }  
49 }  

通過上面的代碼,咱們就求得了每種i和j組合對應的最小乘法次數m[i][j]和對應的最佳分割處s[i][j]。

3、輸出最優構造劃分:

 

       通過運行上面的代碼,咱們就準備好了s[i][j],其中包含最佳分割信息。咱們可使用一種相似於中序遍歷的方法來輸出劃分方式,好比對<A1,A2,A3,A4,A5>和他們對應的下標數組p而言。

void print_optimal_parens(int** s, int i, int j) {  
    if (i == j) {  
        cout << "A" << i;  
    } else {  
        cout << "(";  
        print_optimal_parens(s, i, s[i][j]);  
        print_optimal_parens(s, s[i][j] + 1, j);  
        cout << ")";  
    }  
}  

好比對於數組p={5,6,2,9,7,6}和<A1,A2,A3,A4,A5>通過上面兩段代碼的調用,輸出劃分結果:

 

((A1A2)((A3A4)A5))

 

最少乘法次數爲:330次

                            轉自:http://blog.csdn.net/cyp331203/article/details/42965237

相關文章
相關標籤/搜索