算法導論--動態規劃(矩陣鏈乘法)

矩陣鏈乘法問題

給定一個n個矩陣的序列A1,A2,A3...An ,我們要計算他們的乘積:A1A2A3...An ,由於矩陣乘法滿足結合律,加括號不會影響結果,但是不同的加括號方法,算法複雜度有很大的差別:
考慮矩陣鏈A1,A2,A3 ,三個矩陣規模分別爲10×100100×55×50
如果按((A1A2)A3) 方式,需要做101005=5000 次,再與A3 相乘,又需要10550=2500 ,共需要7500次運算:
如果按(A1A2A3) 方式計算,共需要100550+1010050=75000 次標量乘法,具有10倍的差別。可見一個好的加括號方式,對計算效率有很大影響。
爲了得到所需乘法次數最少的方案,需要計算所有種方案的代價。
對一個n個矩陣的鏈,令P(n) 表示可供選擇的括號化方案的數量。
完全括號化方案的矩陣乘積可以描述爲兩個完全括號化的部分相乘的形式,
P(n)=1 , n=1
P(n)=n1k=1P(k)P(nk) , n2
k爲分割點,即第k個矩陣和第k+1個矩陣之間
可以看出括號化方案的數量與n呈指數關係Ω(2n) ,若採用暴力搜索比較所有括號化方案的代價,效率很差!

動態規劃

1.最優括號化方案的結構特徵

由於要求得矩陣鏈Ai,Ai+1,Ai+2...Aj 的最優括號化方案,我們可以將問題劃分爲兩個子問題Ai,Ai+1...AkAk+1,Ak+2...Aj 的最優括號化方案的組合,這也是可以採用動態規劃的一個重要標示。即一個大的問題的解是其子問題的組合。我們需要遍歷所有的k值ikj1 即考查所用的劃分點。

2.遞歸求解方案

m[i,j],ij 標示矩陣鏈Ai,Ai+1,Ai+2...Aj 的最優括號化方案所需乘法次數的最小值。
i=j 時,m[i,j]=0 ,只有一個矩陣不涉及乘法運算
i<j 時,假設在矩陣鏈Ai,Ai+1,Ai+2...Aj 分割點位置爲AkAk+1 之間,如上分析,m[i,j]m[i,k]m[k+1,j] 的代價和,還要加上兩者最後相乘涉及的運算次數。假如Ai 的大小爲pi1×pi ,則子矩陣鏈m[i,k] 乘積後的矩陣大小爲pi1×pk , m[k+1,j] 乘積後的大小爲pk×pj ,所以最後一次乘積做的乘法運算次數爲pi1pkpj
即:
m[i,j]=0,(i=j)
m[i,j]=min{m[i,k]+m[k+1,j]+pi1pkpj},i<jikj1

3.計算最優代價

遞歸算法會多次遇到同一個子問題,與鋼鐵切割很類似,每一次高層的運算,都會調用底層結果,越是底層,被調用的次數越多。所以可以採用自底向上的方法,先對底層逐個求解,當上層需要調用底層時,底層已經被求解完畢。
用m[i][j]二維矩陣保存對應鏈Ai,Ai+1,Ai+2...Aj 長度爲j-i+1的最優計算代價q。
用s[i][j]二維矩陣保存對應鏈Ai,Ai+1,Ai+2...Aj 長度爲j-i+1的最優劃分位置k。

void Matrix_Chain_Order(int p[],int n)
{
   int i,j,L,k,q;
   for (i=1;i<=n;i++)      //先對單個矩陣的鏈,求解,即所有m[i][i] =0;
   {
     m[i][i]=0;          
   }
   for(L=2;L<=n;L++)     //從兩個矩陣鏈的長度開始,逐次增加矩陣鏈的長度
       for(i=1;i<=n-L+1;i++)  //在給定p[]中的矩陣鏈中,對所有種長度爲L的情況計算
       {
           j = i+L-1;
           m[i][j] = -1;
           for(k=i;k<=j-1;k++)   //遍歷所有可能的劃分點k,計算出最優的劃分方案
           {
             q = m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j];//計算劃分的代價
             if ( q < m[i][j] || m[i][j] == -1)  
             {
                m[i][j] = q;     //最優的代價q保存在m[i][j]中
                s[i][j] = k;     //最優的劃分位置k保存在s[i][j]中
             }
           }
       }

}

構造最優解

矩陣鏈Ai,Ai+1,Ai+2...Aj ,由於二維矩陣s[i][j]記錄了對應劃分位置k,指出了應該在AkAk+1 之間,同樣在矩陣鏈\left[中最優劃分位置一定保存在數組s[i][s[i,j]] 內,矩陣;鏈Ak+1,Ak+2...Aj 的最優劃分位置一定保存在s[s[i][j]+1]][j] 數組內,可以不斷遞歸出最優解。

例程

這裏寫圖片描述

/************************************************************************
CSDN 勿在浮沙築高臺 
http://blog.csdn.net/luoshixian099
算法導論--動態規劃(矩陣鏈乘法)
2015年6月3日                    
************************************************************************/
#include <STDIO.H>
#include <STDLIB.H>
int m[7][7]={0};
int s[7][7]={0};
void Print_Optimal_Parens(int s[][7],int i,int j)  //構造最優解
{
   if ( i ==j)
   {
       printf("A%d",i);
   }
   else
   {
       printf("(");
       Print_Optimal_Parens(s,i,s[i][j]);
       Print_Optimal_Parens(s,s[i][j]+1,j);
       printf(")");
   }
}
void Matrix_Chain_Order(int p[],int n)
{
   int i,j,L,k,q;
   for (i=1;i<=n;i++)      //先對單個矩陣的鏈,求解,即所有m[i][i] =0;
   {
     m[i][i]=0;          
   }
   for(L=2;L<=n;L++)     //從兩個矩陣鏈開始,逐次增加矩陣鏈的長度
       for(i=1;i<=n-L+1;i++)  //在給定p[]中的矩陣鏈中,對所有種長度爲L的情況計算
       {
           j = i+L-1;
           m[i][j] = -1;
           for(k=i;k<=j-1;k++)   //遍歷所有可能的劃分點k,計算出最優的劃分方案,
           {
             q = m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j];
             if ( q < m[i][j] || m[i][j] == -1)  
             {
                m[i][j] = q;     //最優的代價q保存在m[i][j]中
                s[i][j] = k;     //最優的劃分位置k保存在s[i][j]中
             }
           }
       }

}
void main()
{
    int p[]={30,35,15,5,10,20,25};    //矩陣的輸入
    int length = sizeof(p)/sizeof(p[0])-1;   //矩陣長度
    int i,j;
    Matrix_Chain_Order(p,length);

    for(i =1;i<=6;i++)
    {
        for (j=1;j<=6;j++)
        {
            printf("%8d",m[i][j]);
        }
        printf("\n");
    }

    Print_Optimal_Parens(s,1,6);
    printf("\n");
}

這裏寫圖片描述

發佈了57 篇原創文章 · 獲贊 268 · 訪問量 60萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章