動態規划之矩陣連乘


【問題描述】

給定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;另一種是自頂向下填表的備忘錄法,該方法將表的每個元素初始化為某特殊值(本問題中可以將最優乘積代價設置為一極大值),以表示待計算,在遞歸的過程中逐個填入遇到的子問題的解。

 

第一種 自底向上 的方式

 1 #include<iostream>
 2 using namespace std;
 3 
 4 const int N=7;
 5 //p為矩陣鏈,p[0],p[1]代表第一個矩陣,p[1],p[2]代表第二個矩陣,length為p的長度
 6 //所以如果有六個矩陣,length=7,m為存儲最優結果的二維矩陣,s為存儲選擇最優結果路線的
 7 //二維矩陣
 8 void MatrixChainOrder(int *p,int m[N][N],int s[N][N],int length)
 9 {
10     int n=length-1;
11     int l,i,j,k,q=0;
12     //m[i][i]只有一個矩陣,所以相乘次數為0,即m[i][i]=0;
13     for(i=1;i<length;i++)
14     {
15         m[i][i]=0;
16     }
17     //l表示矩陣鏈的長度
18     // l=2時,計算 m[i,i+1],i=1,2,...,n-1 (長度l=2的鏈的最小代價)
19     for(l=2;l<=n;l++)
20     {
21         for(i=1;i<=n-l+1;i++)
22         {
23             j=i+l-1; //以i為起始位置,j為長度為l的鏈的末位,
24             m[i][j]=0x7fffffff;
25             //k從i到j-1,以k為位置划分
26             for(k=i;k<=j-1;k++)
27             {
28                 q=m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j];
29                 if(q<m[i][j])
30                 {
31                     m[i][j]=q;
32                     s[i][j]=k;
33                 }
34             }
35         }
36     }
37     cout << m[1][N-1] << endl;
38 }
39 void PrintAnswer(int s[N][N],int i,int j)
40 {
41     if(i==j)
42     {
43         cout<<"A"<<i;
44     }
45     else
46     {
47         cout<<"(";
48         PrintAnswer(s,i,s[i][j]);
49         PrintAnswer(s,s[i][j]+1,j);
50         cout<<")";
51     }
52 }
53 int main()
54 {
55     int p[N]={30,35,15,5,10,20,25};
56     int m[N][N],s[N][N];
57     MatrixChainOrder(p,m,s,N);
58     PrintAnswer(s,1,N-1);
59     return 0;
60 }

對於 p={30 35 15 5 10 20 25}:

計算順序為:

 

第二種 自頂向下 (備忘錄)的方式

 

#include<iostream>
using namespace std;

const int N=7;

int MatrixChainOrder2(int *p,int m[N][N],int s[N][N],int i, int j)
{
    
    if (i == j)
    {
        return 0;
    }
    if (m[i][j] < 0x7fffffff)
    {
        return m[i][j];
    }

    for (int k=i; k<j; ++k)
    {
        
        int tmp = MatrixChainOrder2(p,m,s,i,k) + MatrixChainOrder2(p,m,s,k+1,j) + p[i-1]*p[k]*p[j];
        if (tmp < m[i][j])
        {
            m[i][j] = tmp;
            s[i][j] = k;
        }
    }
    return m[i][j];
}


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];

    for (int i=0; i<N; ++i)
    {
        for (int j=0; j<N; ++j)
            m[i][j] = 0x7fffffff;
    }
    cout << MatrixChainOrder2(p,m,s,1,6)<<endl;
    PrintAnswer(s,1,N-1);
    return 0;
}

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

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

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

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

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

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


免責聲明!

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



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