算法之矩陣連乘


一.問題描敘

    給定n個矩陣{A1,A2,……,An},其中Ai與Ai+1是可乘的,i=1,2,……,n-1。

   例如:

     計算三個矩陣連乘{A1,A2,A3};維數分別為10*100 , 100*5 , 5*50

     按此順序計算需要的次數((A1*A2)*A3):10X100X5+10X5X50=7500次

     按此順序計算需要的次數(A1*(A2*A3)):10X5X50+10X100X50=75000次

     所以要解決的問題是:如何確定矩陣連乘積A1A2,……An的計算次序,使得按此計算次序計算矩陣連乘積需要的數乘次數達到最小化。

二.問題分析

   由於矩陣乘法滿足結合律,所以計算矩陣連乘的連乘積可以與許多不同的計算計算次序,這種計算次序可以用加括號的方式來確定。若一個矩陣連乘積的計算次序完全確定,也就是說連乘積已完全加括號,那么可以依此次序反復調用2個矩陣相乘的標准算法計算出矩陣連乘積。

   完全加括號的矩陣連乘積可遞歸地定義為:

   (1).單個矩陣是完全加括號的;

  (2).矩陣連乘積A是完全加括號的,則A可以表示為2個完全加括號的矩陣連乘積B和C的乘積並加括號,及A=(BC);

    舉個例子,矩陣連乘積A1A2A3A4A5,可以有5種不同的完全加括號方式:

       (A1(A2(A3A4))),(A1((A2A3)A4)),((A1A2)(A3A4)),((A1(A2A3))A4),(((A1A2)A3)A4)

    每一種完全加括號的方式對應一種矩陣連乘積的計算次序,而矩陣連乘積的計算次序與其計算量有密切的關系,即與矩陣的行和列有關。

    補充一下數學知識,矩陣A與矩陣B可乘的條件為矩陣A的列數等於矩陣B的行數,例如,若A是一個p*q的矩陣,B是一個q*r的矩陣,則其乘積C=AB是一個p*r的矩陣。

三.動態規划解決矩陣連乘積的最優計算次序問題

    或許你對動態規划有點陌生,那簡單的講講什么叫動態規划吧。

    動態規划算法與分治法類似,其基本思想也就是將待求解的問題分解成若干個子問題,先求解子問題,然后從這些子問題的解得到原問題的解,簡單概括為自頂向下分解,自底向上求解。與分治法不同的是,適合於用動態規划法求解的問題,經分解得到的子問題往往不是相互獨立的,換句話說,就是前面解決過的子問題,在后面的子問題中又碰到了前面解決過的子問題,子問題之間是有聯系的。如果用分治法,有些同樣的子問題會被重復計算幾次,這樣就很浪費時間了。所以動態規划是為了解決分治法的弊端而提出的,動態規划的基本思想就是,用一個表來記錄所有已經解決過的子問題的答案,不管該子問題在以后是否會被用到,只要它被計算過,就將其結果填入表中,以后碰到同樣的子問題,就可以從表中直接調用該子問題的答案,而不需要再計算一次。具體的動態規划的算法多種多樣,但他們都具有相同的填表式。

   順便說一下動態規划的適用場合,一般適用於解最優化問題,例如矩陣連乘問題、最長公共子序列、背包問題等等,通常動態規划的設計有4個步驟,結合矩陣連乘分析:

   (1).找出最優解的性質,並刻畫其結構特征

    這是設計動態規划算法的第一步,我們可以將矩陣連乘積AiAi+1……Aj記為A[i:j]。問題就是計算A[1:n]的最優計算次序。設這個計算次序在矩陣Ak和Ak+1之間將矩陣鏈斷開,1<=k<n,使其完全加括號方式為((A1……Ak)(AK+1……An)),這樣就將原問題分解為兩個子問題,,按此計算次序,計算A[1:n]的計算量就等於計算A[1:k]的計算量加上A[k+1:n]的計算量,再加上A[1:k]和A[k+1:n]相乘的計算量。計算A[1:n]的最優次序包含了計算A[1:k]和A[k+1:n]這兩個子問題的最優計算次序,以此類推,將A[1:k]和A[k+1:n]遞歸的分解下去,求出每個子問題的最優解,子問題的最優解相乘便得到原問題的最優解。

   (2).遞歸地定義最優值

           這是動態規划的第二步,對於矩陣連乘積的最優計算次序的問題,設計算A[i:j],1<=i<=j<=n,所需要的最小數乘次數為m[i][j],則原問題的最優值為m[1][n]。

           當i=j時,A[i:j]=Ai為單一的矩陣,則無需計算,所以m[i][j]=0,i=j=1,2,……,n。即對應的二維表對角線上的值全為0。

           當i<j時,這就需要用步驟(1)的最優子結構性質來計算m[i][j]。若計算A[i:j]的最優次序在Ak和Ak+1之間斷開,i<=k<j,則m[i][j]=m[i][k]+m[k+1][j]+pi-1*pk*pj,k的位置只有j-i種可能,即k屬於集合{i,i+1,……,j-1},所以k是這j-i個位置中使計算量達到最小的那個位置。

          所以m[i][j]可以遞歸地定義為        m[i][j]={  0                                                            i=j

                                                                      min{m[i][k]+m[k+1][j]+pi-1*pk*pj }         i<j ,i<=k<j     }

         將對應於m[i][j]的斷開位置k記為s[i][j],在計算出最優值m[i][j]后,可遞歸地由s[i][j]構造出相應的最優解

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

          動態規划的一大好處是,在計算的過程中,將已解決的子問題答案保存起來,每個子問題只計算一次,而后面的子問題需要用到前面已經解決的子問題,就可以從表中簡單差出來,從而避免了大量的重復計算

         動態規划算法 這里的p[],m[][],s[][]都為全局變量

 1 void matrixChain(){
 2     for(int i=1;i<=n;i++)m[i][i]=0;
 3 
 4     for(int r=2;r<=n;r++)//對角線循環
 5         for(int i=1;i<=n-r+1;i++){//行循環
 6             int j = r+i-1;//列的控制
 7             //找m[i][j]的最小值,先初始化一下,令k=i
 8             m[i][j]=m[i][i]+m[i+1][j]+p[i-1]*p[i]*p[j];
 9             s[i][j]=i;
10             //k從i+1到j-1循環找m[i][j]的最小值
11             for(int k = i+1;k<j;k++){
12                 int temp=m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j];
13                 if(temp<m[i][j]){
14                     m[i][j]=temp;
15                     //s[][]用來記錄在子序列i-j段中,在k位置處
16                         斷開能得到最優解
17                     s[i][j]=k;
18                 }
19             }
20         }
21 }

        以A1A2A3A4A5A6為例,其中各矩陣的維數分別為:

      A1:30*35,  A2:35*15,  A3:15*5,  A4:5*10,  A5:10*20,  A6:20*25

      動態規划算法matrixchain計算m[ i ][ j ]先后次序如圖所示,計算結果為m[ i ][ j ]和s[ i ][ j ],其中第0行和第0列沒有使用。

     

            計算次序                                                      m[i][j]                                                             s[i][j]

     

              例如,在計算m[2][5]時,依遞歸式有

                  

               所以m[2][5] = 7125,且k=3,因此,s[2][5]=3。

   (4).根據計算最優值時得到的信息(及存放最優值的表格),構造最優解

         動態規划算法的第四布是構造問題的最優解。算法matrixchain只是計算出了最優值,並未給出最優解。也就是說,通過matrixchain的計算,只是到最少數乘次數,還不知道具體應按什么次序來做矩陣乘法才能達到最少的數乘次數。

        下面一段小程序按matrixchain計算出的斷點矩陣s指示的加括號方式輸出計算A[i:j]的最優計算次序

       

void printmatrix(int leftindex,int rightindex)//遞歸打印輸出  
{  
    if(leftindex==rightindex)  
        printf("A%d",leftindex);  
    else{  
        printf("(");  
        printmatrix(leftindex,leftindex+s[leftindex][rightindex]);  
        printmatrix(leftindex+s[leftindex][rightindex]+1,rightindex);  
        printf(")");  
    }  

  到此,矩陣連乘問題就解決完了。

 四.源代碼展示及運算結果

#include <stdio.h>  
#include <stdlib.h>  
#include <limits.h>  
#define MAX 50
int p[MAX+1];             //存儲各個矩陣的列數以及第一個矩陣的行數(作為第0個矩陣的列數) 
int m[MAX][MAX];          //m[i][j]存儲子問題的最優解  
int s[MAX][MAX];           //s[i][j]存儲子問題的最佳分割點
int n;                    //矩陣個數
 
 void matrix(int n,int m[][n],int s[][n],int p[])  
{  
   
    int i,j,k;  
    for(i=0;i<n;i++)  
        m[i][i]=0;                  //最小子問題僅含有一個矩陣 ,對角線全為0 
                               
    for(i=2;i<=n;i++)  
        for(j=0;j<n-i+1;j++)
        {                         
            m[j][j+i-1]=INT_MAX;   
            for(k=0;k<i-1;k++)
            {                                    //k代表分割點  
                if(m[j][j+i-1]>m[j][j+k]+m[j+k+1][j+i-1]+p[j]*p[j+k+1]*p[j+i])
                {  
                    m[j][j+i-1]=m[j][j+k]+m[j+k+1][j+i-1]+p[j]*p[j+k+1]*p[j+i];  
                    s[j][j+i-1]=k;                           //記錄分割點  
                }  
            }  
        }  
}  
  
 void printmatrix(int leftindex,int rightindex)//遞歸打印輸出  
{  
    if(leftindex==rightindex)  
        printf("A%d",leftindex);  
    else{  
        printf("(");  
        printmatrix(leftindex,leftindex+s[leftindex][rightindex]);  
        printmatrix(leftindex+s[leftindex][rightindex]+1,rightindex);  
        printf(")");  
    }  
}  
int main()  
{  
    int i;
    printf("請輸入矩陣相乘的矩陣個數");
    scanf("%d",&n);
    printf("請依次輸入矩陣的行和烈(如A*B,A=20*30,B=30*40,即輸入20 30 40)\n") ;
    for(i=0;i<n+1;i++)
    {
        scanf("%d",&p[i]);
    }
    matrix(n,m,s,p);  
    printf("矩陣連乘最小次數\t%d\n",m[0][n-1]);  
    printmatrix(0,n-1);  
    printf("\n");  
    return 0;  
}  

運行結果

 

 

    

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM