1.矩陣連乘問題的定義ios
1.1 給定 n 個矩陣的連乘積 A1A2...An,由於矩陣乘法知足結合律,因此計算矩陣的連乘積能夠有不一樣的計算次序(這個次序的組合數知足卡特蘭數),採用不一樣的計算次序計算的數乘次數也不相同。例如,A1A2A3,這三個矩陣的維數分別是10*100,100*5,和5*50,若先計算A1A2,總的計算次數爲10*100*5+10*5*50 = 7500,然而先計算A2A3,總的計算次數爲 100*5*50 + 10*100*50 = 75000,可見計算數乘次數相差10倍。這裏咱們用加括號的方式來表示矩陣的計算次序。每一種徹底加括號方式對應一種矩陣的計算次序;
算法
1.2 什麼是徹底加括號的矩陣連乘積?徹底加括號的矩陣連乘積能夠遞歸的定義爲(見《計算機算法分析與設計》):數組
a)單個矩陣是徹底加括號的;spa
b)矩陣連乘積 A 是徹底加括號的,則 A 能夠表示爲 2 個徹底加括號的矩陣連乘積 B 和 C 的乘積並加括號,即 A =(BC)。設計
2.矩陣連乘問題的求解blog
2.1 設矩陣連乘積A1A2...An,Ai的維數分別爲 p[i-1]*p[i],m[i][j]爲AiAi+1...Aj的最少數乘次數,它知足下述遞歸關係:遞歸
a)m[i][j] = 0 , i == j;ci
b)m[i][j] = min{m[i][k] + m[k+1][j] + p[i-1] * p[k] * p[j]}, i<= k < j;string
2.2 根據上述遞歸式能夠寫出本問題的遞歸方法,這裏爲了求出最終的計算次序,須要用一個二維數組 s 保存每次斷開的位置。io
#include <iostream> #include <string> #include <vector> #include <string.h> #define MAX 100 using namespace std; int MatrixChain(int i, int j, int *p, int (*m)[MAX], int (*s)[MAX]); void TraceBack(int i, int j, int (*s)[MAX]); int main(int argc, const char *argv[]) { int p[MAX], m[MAX][MAX], s[MAX][MAX]; int n; cin >> n; //矩陣的個數 int i ; for(i = 0; i <= n; i++){ //相乘矩陣鏈的行列數 cin >> p[i]; } memset(m, -1, sizeof m); MatrixChain(1, n, p, m, s); TraceBack(1, n, s); cout << endl; return 0; } int MatrixChain(int i, int j, int *p, int (*m)[MAX], int (*s)[MAX]){ if(m[i][j] != -1) return m[i][j]; if(i == j) return 0; else{ int k, min = 1000000; for(k = i; k < j; k++){ m[i][k] = MatrixChain(i, k, p, m, s); m[k+1][j] = MatrixChain(k+1, j, p, m, s); int tmp = m[i][k] + m[k+1][j] + p[i-1] * p[k] * p[j]; if(tmp < min){ min = tmp; s[i][j] = k; } } return min; } } void TraceBack(int i, int j, int (*s)[MAX]){ if(i == j){ cout << "A" << i; return; } cout << "("; TraceBack(i, s[i][j], s); cout << "*"; TraceBack(s[i][j] + 1, j, s); cout << ")"; }
2.3 矩陣連乘的非遞歸方法其實就是一個填充表格的過程,以6個矩陣的乘積爲例,矩陣的維度分別爲p[] = {30, 35, 15, 5, 10, 20, 25},以下圖所示,這裏先將 m[i][i] 設置爲 0, 以後再以正對角線方向根據上述遞歸式計算m[1][2],m[2][3]...m[5][6]等等,例如m[2][3] = m[2][2] + m[3][3] + p[1] * p[2] *p[3] = 0 + 0 + 30 * 35 * 15 = 15750,這裏只能以2分割, 再例如計算 m[2][5] ,此時要分別計算出 m[2][2] + m[3][5] + p[1] * p[2] * p[5], m[2][3] + m[4][5] + p[1] * p[3] * p[5], 以及 m[2][4] + m[5][5] + p[1] * p[4] * p[5],而後求其最小值即爲 m[2][5]。填充後的表格和源程序以下:
1 | 2 | 3 | 4 | 5 | 6 | |
1 | 0 | 15750 | 7875 | 9375 | 11875 | 15125 |
2 | 0 | 2625 | 4375 | 7125 | 10500 | |
3 | 0 | 750 | 2500 | 5375 | ||
4 | 0 | 1000 | 3500 | |||
5 | 0 | 5000 | ||||
6 | 0 |
#include <iostream> #include <string> #include <vector> #define MAX 100 using namespace std; void MatrixChain(int *p, int n, int (*m)[MAX], int (*s)[MAX]); void TraceBack(int i, int j, int (*s)[MAX]); int main(int argc, const char *argv[]) { int p[MAX], m[MAX][MAX], s[MAX][MAX]; int n; cin >> n; //矩陣的個數 int i ; for(i = 0; i <= n; i++){ //相乘矩陣鏈的行列數 cin >> p[i]; } MatrixChain(p, n, m, s); TraceBack(1, n, s); cout << endl; return 0; } void MatrixChain(int *p, int n, int (*m)[MAX], int (*s)[MAX]){ int i; for(i = 1; i <= n; i++) m[i][i] = 0; int r; //外層循環的次數 for(r = 2; r <= n; r++){ for(i = 1; i<= n - r + 1; i++){ //求解m[i][j] int j = i + r -1; int min = m[i][i] + m[i+1][j] + p[i-1] * p[i] * p[j]; s[i][j] = i; int k; for(k = i + 1; k < j; k++){ int tmp = m[i][k] + m[k+1][j] + p[i-1]*p[k]*p[j]; if(tmp < min){ min = tmp; s[i][j] = k; } } m[i][j] = min; } } } void TraceBack(int i, int j, int (*s)[MAX]){ if(i == j){ cout << "A" << i; return; } cout << "("; TraceBack(i, s[i][j], s); cout << "*"; TraceBack(s[i][j] + 1, j, s); cout << ")"; }
3.小結
3.1 和最長公共子序列問題相同,當咱們使用動態規劃來求解某一個問題時,這個問題通常都具備兩個明顯特徵,一是最優子結構性質,即問題的最優解包含了子問題的最優解,二是子問題重疊,即在遞歸求解時,有些子問題被重複計算。
3.2 針對上述子問題重疊的性質,咱們使用了備忘錄方法,即在遞歸的過程當中將每一個子問題的結果保存在數組中,若是下次須要直接從數組中取出,從而避免了重複計算。