問題描述:給定n個矩陣(A1,A2,A3.....An},其中Ai與Ai+1是可乘的,i=1,2,...n-1。考察n個矩陣的連乘積A1A2A3,....An。由於矩陣乘法滿足結合律,故計算矩陣的連乘積可以有許多不同的計算次序,這種計算次序可以用加括號的方式來確定。加括號的方式決定了整個計算量(指的是乘法調用的次數)。所以自然會提出矩陣連乘積的最優計算次序問題。
自然,首先想到的是用枚舉法,算出每一種加括號情況下的計算量,取最小的情況。工程之龐大可想而知。溯其源,會發現,"枚舉“的這種想法是不可避免的,只有所有情況都考慮比較后,才會出現那個最小量乘的結果。普通的枚舉導致龐大工程的一個重要因素就是”子問題重復計算“。這里先要明確,什么是矩陣連乘的子問題。
以A1A2A3A4為例,它的子問題為:A1 A2 A3 A4 A1A2 A2A3 A3A4 A1A2A3 A2A3A4 A1A2A3A4 。你要求A1A2A3A4的最優次序,勢必要先求段長為3的子問題的最優次序,而段長為3的子問題是基於段長為2的子問題的基礎之上的(這就是一種自底向上的遞歸)。以此推下去,你很容易會發現兩個有意思的現象:第一,假如你已計算出段長為3的子問題的最優次序,那該最優次序下的子問題也是最優的(你可以通過反證法獲知);第二,計算完段長為2的子問題,再計算段長為3的子問題時,你還會用到段長為2的子問題的計算結果,那何不把先前的計算結果進行保存,避免重復計算。
以下就是動態規划算法解決矩陣連乘問題的相關代碼,思想無非就兩點:
第一,自底向上的遞歸式:
需要指出的是:m[i][j]表示Ai.....Aj的最少數乘次數,k表示求解Ai....Aj的子問題最優值時的斷開位置,Pi-1PkPj表示AiAi+1.....Ak和Ak+1....Aj相乘時數乘數。
第二,在計算過程中,保存已解決的子問題答案。
#include<iostream>
using namespace std;
void outPut(int i,int j,int **s)
{
if(i==j)
return;
outPut(i,s[i][j],s);
outPut(s[i][j]+1,j,s);
cout<<"Multiply A "<<i<<","<<s[i][j];
cout<<" and A"<<s[i][j]+1<<","<<j<<endl;
}
void MartrixChain(int n,int *p,int **m,int **s)
{
for(int t=0;t<n;t++)
{
m[t][t]=0; //單一矩陣的情況 一個矩陣數乘次數為0
s[t][t]=-1;
}
for(int l=2;l<=n;l++) //l為段長
{
for(int i=0;i<=n-l;i++)
{
int j=i+l-1; //j為每段的起點
m[i][j]=m[i][i]+m[i+1][j]+p[i]*p[i+1]*p[j+1]; //類似於賦初值的功能,其可取i<=k<j中的任意一個
s[i][j]=i;
for(int k=i+1;k<j;k++) //改變斷點,試探出最小的情況
{
if(m[i][k]+m[k+1][j]+p[i]*p[k+1]*p[j+1]<m[i][j])
{
m[i][j]=m[i][k]+m[k+1][j]+p[i]*p[k+1]*p[j+1];
s[i][j]=k; //記錄斷點位置
}
}
}
}
cout<<"\n最少數乘為 "<<m[0][n-1]<<endl<<endl;
outPut(0,n-1,s);
}
int main()
{
int num;
int *dimension;
int **mm;
int **ss;
cin>>num;
dimension=new int[num+1]; //矩陣維數
mm=new int*[num];
ss=new int*[num];
for(int i=0;i<num;i++)
{
mm[i]=new int[num];
ss[i]=new int[num];
}
for(int r=0;r<=num;r++)
cin>>dimension[r];
MartrixChain(num,dimension,mm,ss);
}
程序實現並不難,但是還是要交代幾點容易犯錯的細節:
1.表示矩陣維數的數組大小:應該是矩陣個數+1,理由......呵呵;
2.若Ai表示連乘矩陣中第i個矩陣,請你時刻記住,實際上的數組下標是從0開始的;(程序中,我是令i從0開始的)
這兩點都可以通過調試修正,只是如果一開始就能想明白,沒必要花這種時間。
運行結果如下:

我來解釋下運行結果 A 1 , 1 and A 2 , 2 表示(A1A2)是一個分塊的 依次為(A1A2) (A0(A1A2)) (A3A4) ((A3A4)A5) (A0A1A2)(A3A4A5) 綜合考慮后,最后加括號的方式為:(( A0 ( A1 A2 ) ) ( ( A3 A4 ) A5 ) ) 。
最后,來總結下動態規划算法的要素:1.最優子結構的性質 2.重疊子問題性質
(在此不再贅述,從問題分析中已經體現)。
特別想交代下的是,我在問題分析中提到”枚舉“一詞,個人覺得動態規划就是”變相的枚舉“,只是它通過小規模的一步步枚舉在縮小范圍,進而減少重復枚舉,能達到這個目的就是基於上述的兩個要素,而動態規划能得到正確的答案,則是因為它已經考慮比較了所有可能的情況(這也是解決任何問題所不能避免的,只是有些是隱式比較而已)。
還有一個非常欠缺的地方:怎么樣直接輸出加括號的表達式呢?如(( A0 ( A1 A2 ) ) ( ( A3 A4 ) A5 ) ) 。如果無視空間的代價,多開幾個數組,用上隊列的思想,是可以實現,能不能有更簡潔的方法呢?~~~communicating!!!!!!!!
