1. 程式人生 > >矩陣連乘最少次數

矩陣連乘最少次數

一.問題描敘

    給定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[][]都為全域性變數

 以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的計算,只是到最少數乘次數,還不知道具體應按什麼次序來做矩陣乘法才能達到最少的數乘次數。

****************************************************************************************************************************

模板:

#include <stdio.h>  
#include <stdlib.h>   
#define MAX 50
#define inf 99999999
int p[MAX+1];             //儲存各個矩陣的列數以及第一個矩陣的行數(作為第0個矩陣的列數) 
int m[MAX][MAX];          //m[i][j]儲存子問題的最優解  
int s[MAX][MAX];           //s[i][j]儲存子問題的最佳分割點
int n;                    //矩陣個數
 void matrix()  
{  
   
    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]=inf;   
            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();  
    printf("矩陣連乘最小次數\t%d\n",m[0][n-1]);  
    printmatrix(0,n-1);  
    printf("\n");  
    return 0;  
}