動態規划之矩陣連乘問題


【問題描述】

給定n個矩陣{A1,A2,…,An},其中Ai與Ai+1是可乘的,i=1,2…,n-1。如何確定計算矩陣連乘積的計算次序,使得依此次序計算矩陣連乘積需要的數乘次數最少。例如,給定三個連乘矩陣{A1,A2,A3}的維數分別是10*100,100*5和5*50,采用(A1A2)A3,乘法次數為10*100*5+10*5*50=7500次,而采用A1(A2A3),乘法次數為100*5*50+10*100*50=75000次乘法,顯然,最好的次序是(A1A2)A3,乘法次數為7500次。

分析:
矩陣鏈乘法問題描述:
給定由n個矩陣構成的序列{A1,A2,...,An},對乘積A1A2...An,找到最小化乘法次數的加括號方法。

1)尋找最優子結構
此問題最難的地方在於找到最優子結構。對乘積A1A2...An的任意加括號方法都會將序列在某個地方分成兩部分,也就是最后一次乘法計算的地方,我們將這個位置記為k,也就是說首先計算A1...Ak和Ak+1...An,然后再將這兩部分的結果相乘。
最優子結構如下:假設A1A2...An的一個最優加括號把乘積在Ak和Ak+1間分開,則前綴子鏈A1...Ak的加括號方式必定為A1...Ak的一個最優加括號,后綴子鏈同理。
一開始並不知道k的確切位置,需要遍歷所有位置以保證找到合適的k來分割乘積。

2)構造遞歸解
設m[i,j]為矩陣鏈Ai...Aj的最優解的代價,則

3)構建輔助表,解決重疊子問題
從第二步的遞歸式可以發現解的過程中會有很多重疊子問題,可以用一個nXn維的輔助表m[n][n] s[n][n]分別表示最優乘積代價及其分割位置k
輔助表s[n][n]可以由2種方法構造,一種是自底向上填表構建,該方法要求按照遞增的方式逐步填寫子問題的解,也就是先計算長度為2的所有矩陣鏈的解,然后計算長度3的矩陣鏈,直到長度n;另一種是自頂向下填表的備忘錄法,該方法將表的每個元素初始化為某特殊值(本問題中可以將最優乘積代價設置為一極大值),以表示待計算,在遞歸的過程中逐個填入遇到的子問題的解。

 

對於一組矩陣:A1(30x35),A2(35x15),A3(15x5),A4(5x10),A5(10x20),A6(20x25)    個數N為6

那么p數組保存它們的行數和列數:p={30,35,15,5,10,20,25}共有N+1即7個元素

p[0],p[1]代表第一個矩陣的行數和列數,p[1],p[2]代表第二個矩陣的行數和列數......p[5],p[6]代表第六個矩陣的行數和列數

計算順序為:

輔助表m: m[i][j]代表從矩陣Ai,Ai+1,Ai+2......直到矩陣Aj最小的相乘次數,比如A[2][5]代表A2A3A4A5最小的相乘次數,即最優的乘積代價。我們看上圖,從矩陣A2到A5有三種斷鏈方式:A2{A3A4A5}、{A2A3}{A4A5}、{A2A3A4}A5,這三種斷鏈方式會影響最終矩陣相乘的計算次數,我們分別算出來,然后選一個最小的,就是m[2][5]的值,同時保留斷開的位置k在s數組中。

源代碼(自底向上的方式)(還有自低向下方式)

#include<iostream>
using namespace std;
 
const int N=7;
//p為矩陣鏈,p[0],p[1]代表第一個矩陣的行數和列數,p[1],p[2]代表第二個矩陣的行數和列數......length為p的長度
//所以如果有六個矩陣,length=7,m為存儲最優結果的二維矩陣,s為存儲選擇最優結果路線的
//二維矩陣
void MatrixChainOrder(int *p,int m[N][N],int s[N][N],int length)
{
    int n=length-1;
    int l,i,j,k,q=0;
    //m[i][i]只有一個矩陣,所以相乘次數為0,即m[i][i]=0;
    for(i=1;i<length;i++)
    {
        m[i][i]=0;
    }
    //l表示矩陣鏈的長度
    // l=2時,計算 m[i,i+1],i=1,2,...,n-1 (長度l=2的鏈的最小代價)
    for(l=2;l<=n;l++)
    {
        for(i=1;i<=n-l+1;i++)
        {
            j=i+l-1; //以i為起始位置,j為長度為l的鏈的末位,
            m[i][j]=0x7fffffff;
            //k從i到j-1,以k為位置划分
            for(k=i;k<=j-1;k++)
            {
                q=m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j];
                if(q<m[i][j])
                {
                    m[i][j]=q;
                    s[i][j]=k;
                }
            }
        }
    }
    cout << m[1][N-1] << endl;
}
void PrintAnswer(int s[N][N],int i,int j)
{
    if(i==j)
    {
        cout<<"A"<<i;
    }
    else
    {
        cout<<"(";
        PrintAnswer(s,i,s[i][j]);
        PrintAnswer(s,s[i][j]+1,j);
        cout<<")";
    }
}
int main()
{
    int p[N]={30,35,15,5,10,20,25};
    int m[N][N],s[N][N];
    MatrixChainOrder(p,m,s,N);
    PrintAnswer(s,1,N-1);
    return 0;
}

運行結果:

 注意:

矩陣連乘計算次序問題的最優解包含着其子問題的最優解。這種性質稱為最優子結構性質。(全局最優解蘊含局部最優解) 

•在分析問題的最優子結構性質時,所用的方法具有普遍性:首先假設由問題的最優解導出的子問題的解不是最優的,然后再設法說明在這個假設下可構造出比原問題最優解更好的解,從而導致矛盾。 

•利用問題的最優子結構性質,以自底向上的方式遞歸地從子問題的最優解逐步構造出整個問題的最優解。最優子結構是問題能用動態規划算法求解的前提。

•遞歸算法求解問題時,每次產生的子問題並不總是新問題,有些子問題被反復計算多次。這種性質稱為子問題的重疊性質。 

•動態規划算法,對每一個子問題只解一次,而后將其解保存在一個表格中,當再次需要解此子問題時,只是簡單地用常數時間查看一下結果。 

•通常不同的子問題個數隨問題的大小呈多項式增長。因此用動態規划算法只需要多項式時間,從而獲得較高的解題效率。

參考:王曉東《算法設計與分析》

           https://www.cnblogs.com/scarecrow-blog/p/3712580.html


免責聲明!

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



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