《問題的引出》
看下面一個例子,計算三個矩陣連乘{A1,A2,A3};維數分別爲10*100 , 100*5 , 5*50
按此順序計算需要的次數((A1*A2)*A3):10X100X5+10X5X50=7500次
按此順序計算需要的次數(A1*(A2*A3)):10X5X50+10X100X50=75000次
所以問題是:如何確定運算順序,可以使計算量達到最小化。
枚舉顯然不可,如果枚舉的話,相當於一個“完全加括號問題”,次數爲卡特蘭數,卡特蘭數指數增長,必然不行。
《建立遞歸關係》
子問題狀態的建模(很關鍵):令m[i][j]表示第i個矩陣至第j個矩陣這段的最優解。
顯然如果i=j,則m[i][j]這段中就一個矩陣,需要計算的次數爲0;
如果i>j,則m[i][j]=min{m[i][k]+m[k+1][j]+p[i-1]Xp[k]Xp[j]},其中k,在i與j之間遊蕩,所以i<=k<j ;
代碼實現時需要注意的問題:計算順序!!!
因爲你要保證在計算m[i][j]查找m[i][k]和m[k+1][j]的時候,m[i][k]和m[k+1][j]已經計算出來了。
觀察座標的關係如圖:
所以計算順序如上右圖:相應的計算順序對應代碼爲13-15行
m[1][n]即爲最終求解,最終的輸出想爲((A1(A2 A3))((A4 A5)A6))的形式,不過沒有成功,待思考...
1 #include<iostream>
2 using namespace std;
3 const int MAX = 100;
4 //p用來記錄矩陣的行列,main函數中有說明
5 //m[i][j]用來記錄第i個矩陣至第j個矩陣的最優解
6 //s[][]用來記錄從哪裏斷開的纔可得到該最優解
7 int p[MAX+1],m[MAX][MAX],s[MAX][MAX];
8 int n;//矩陣個數
9
10 void matrixChain(){
11 for(int i=1;i<=n;i++)m[i][i]=0;
12
13 for(int r=2;r<=n;r++)//對角線循環
14 for(int i=1;i<=n-r+1;i++){//行循環
15 int j = r+i-1;//列的控制
16 //找m[i][j]的最小值,先初始化一下,令k=i
17 m[i][j]=m[i][i]+m[i+1][j]+p[i-1]*p[i]*p[j];
18 s[i][j]=i;
19 //k從i+1到j-1循環找m[i][j]的最小值
20 for(int k = i+1;k<j;k++){
21 int temp=m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j];
22 if(temp<m[i][j]){
23 m[i][j]=temp;
24 //s[][]用來記錄在子序列i-j段中,在k位置處
25 //斷開能得到最優解
26 s[i][j]=k;
27 }
28 }
29 }
30 }
31
32 //根據s[][]記錄的各個子段的最優解,將其輸出
33 void traceback(int i,int j){
34 if(i==j)return ;
35
36 traceback(i,s[i][j]);
37 traceback(s[i][j]+1,j);
38 cout<<"Multiply A"<<i<<","<<s[i][j]<<"and A"<<s[i][j]+1<<","<<j<<endl;
39 }
40
41 int main(){
42 cin>>n;
43 for(int i=0;i<=n;i++)cin>>p[i];
44 //測試數據可以設爲六個矩陣分別爲
45 //A1[30*35],A2[35*15],A3[15*5],A4[5*10],A5[10*20],A6[20*25]
46 //則p[0-6]={30,35,15,5,10,20,25}
47 //輸入:6 30 35 15 5 10 20 25
48 matrixChain();
49
50 traceback(1,n);
51 //最終解值爲m[1][n];
52 cout<<m[1][n]<<endl;
53 return 0;
54 }