矩陣連乘問題
若矩陣A是一個p*q的矩陣,B是一個q*r的矩陣,則C=AB,是一個p*r的矩陣,需進行pqr次數乘計算。
存在{A1,A2,A3}三個矩陣,維數分別為100*5,5*50,50*10。若直接相乘,A1*A2*A3,則需要進行n=100*5*50+100*50*10=25000+50000=75000次數乘計算。如果我們調整運算順序,A1*(A2*A3),則需要進行n=5*50*10+100*5*10=2500+5000=7500次數乘計算。
由此可見,當進行矩陣連乘運算時,加括號的方式,即計算次序對計算量有很大的影響。
代碼展示:
1 #include<iostream> 2 3 using namespace std; 4 /* 5 自底向上的推出矩陣連乘的最優解 6 先從兩個矩陣相乘開始,而后三個矩陣相乘,四個......直到推出目標長度的最優解 ,即假設一個矩陣鏈,初始長度為2,算出所有相鄰矩陣相乘的計算次數,而后使其長度為3...4...直到目標長度 7 狀態轉移方程: 8 m[i][j]=min {m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j]} i<=k<j i<j 9 m[i][j]=0 i==j; 10 */ 11 #define LEN 5 //矩陣個數 12 //矩陣連乘函數,找到最優解 13 void MatrixChain(int *p, int m[][LEN + 1], int s[][LEN + 1]) { 14 for (int i = 0; i < LEN + 1; i++) m[i][i] = 0; //初始化,對角線元素置零,即當矩陣鏈長度為1時(只有一個矩陣)不用乘,為零 15 for (int r = 2; r <= LEN; r++) { //r表示矩陣鏈的長度,從2開始,兩個矩陣相乘,而后3...4...5... 16 for (int i = 1; i <= LEN - r + 1; i++) { //i是矩陣鏈的首個矩陣,小於矩陣個數減矩陣鏈長度加一 17 int j = i + r - 1; //j是矩陣鏈的最后一個元素 18 m[i][j] = m[i][i] + m[i + 1][j] + p[i - 1] * p[i] * p[j]; //m[i][j]是子結構,從最左邊開始推 19 s[i][j] = i; //標記斷開的位置 20 for (int k = i + 1; k < j; k++) { //k是i和j直接的斷開點,是在i和j之間的子結構 ,通過k的循環找到最優的解 21 int t = m[i][k] + m[k + 1][j] + p[i - 1] * p[k] * p[j]; //狀態轉移方程 22 if (t < m[i][j]) { 23 m[i][j] = t; //更新最優解 24 s[i][j] = k; //更新斷開點 25 } 26 } 27 } 28 } 29 } 30 31 //回溯函數,根據s[i][j]數組標記的位置,回溯找到斷開的位置 32 void Traceback(int i, int j, int s[][LEN + 1]) { 33 if (i == j) { //當i與j相等 說明回溯到該矩陣的位置了 34 cout << "A" << i; 35 } 36 else { 37 cout << "("; 38 Traceback(i, s[i][j], s); //從尾往頭回溯 39 Traceback(s[i][j] + 1, j, s); //從斷點往后回溯 40 cout << ")"; 41 } 42 } 43 //輸出函數 44 void output(int t[][LEN + 1]) { 45 for (int i = 1; i <= LEN; i++) { 46 for (int j = 1; j <= LEN; j++) { 47 cout << " " << t[i][j] << " "; 48 } 49 cout << endl; 50 } 51 } 52 int main(void) { 53 int p[LEN + 1] = { 6,8,9,3,4,10 }; //矩陣的維度分別是2*3,3*4,4*5,5*6,6*7,LEN+1個數表示LEN個矩陣 54 int m[LEN + 1][LEN + 1] = { 0 }; //記錄最優子結構的二維數組 55 int s[LEN + 1][LEN + 1] = { 0 }; //記錄最優解對應的括號的位置 56 57 MatrixChain(p, m, s); 58 59 cout << endl; 60 output(m); 61 cout << endl; 62 output(s); 63 cout << endl; 64 cout << "outcome:" <<endl; 65 Traceback(1, LEN, s); 66 cout << endl; 67 68 return 0; 69 }
運行結果:
與備忘錄方法的區別:
我們使用的動態規划方法中其實融入了備忘錄的一些東西,我們的m和s數組都是用來記錄的,所以備忘錄方法與我們使用的方法類似,不同在於,我們是自底向上的,而備忘錄方法是自頂向下的進行。