【問題描述】
給定有n個連乘矩陣的維數,要求計算其採用最優計算次序時所用的乘法次數,即所要求計算的乘法次數最少。例如,給定三個連乘矩陣{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的最優解的代價,則
┌ 0 如果i = j
m[i,j] = │
└ min(i≤k<j) {m[i,k] + m[k+1,j] + p[i-1] * p[k] *p[j] } 如果i<j
3)構建輔助表,解決重疊子問題
從第二步的遞迴式可以發現解的過程中會有很多重疊子問題,可以用一個nXn維的輔助表m[n][n] s[n][n]分別表示最優乘積代價及其分割位置k 。
輔助表s[n][n]可以由2種方法構造,一種是自底向上填表構建,該方法要求按照遞增的方式逐步填寫子問題的解,也就是先計算長度為2的所有矩陣鏈的解,然後計算長度3的矩陣鏈,直到長度n;另一種是自頂向下填表的備忘錄法,該方法將表的每個元素初始化為某特殊值(本問題中可以將最優乘積代價設定為一極大值),以表示待計算,在遞迴的過程中逐個填入遇到的子問題的解。
#include<iostream>using namespace std;#define N 7//p為矩陣鏈,p[0],p[1]代表第一個矩陣,p[1],p[2]代表第二個矩陣,length為p的長度//所以如果有六個矩陣,length=7,m為儲存最優結果的二維矩陣,s為儲存選擇最優結果路線的//二維矩陣void MatrixChainOrder(int *p,int (*m)[N],int (*s)[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],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]={20,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;}