1.計算連個矩陣乘積的標准算法: //標准算法 void MatrixMultiply(int a[][MAXN], int b[][MAXN], int p, int q, int r) { int sum[MAXN][MAXN]; memset(sum, 0, sizeof(sum));
int i, j, k; //遍歷矩陣a的行 for (k = 0; k < p; k++) { //遍歷矩陣b的列 for (j = 0; j < r; j++) { //對應位置相乘 for (i = 0; i < q; i++) { sum[k][j] += a[k][i] * b[i][j]; } } } } 所以A、B兩個矩陣相乘的計算量為p*q*r。
2. 計算連個矩陣乘積的動態規划算法: #include <stdio.h> #include <stdlib.h> #include<Windows.h>
#define MAX 100
int matrix_chain(int *p, int n, int **m, int **s) { //a[][]最小乘次數 //s[][]最小乘數時的斷開點 int i,j,r,k;
for (i = 0; i < n; i++) //單一矩陣的最小乘次都置為0 { m[i][i] = 0; }
for (r = 2; r <= n; r++) //r為連乘矩陣的個數 { for (i = 0; i <= n-r; i++) //i表示連乘矩陣中的第一個 { j = i + r -1; //j表示連乘矩陣中的最后一個 m[i][j] = 99999; for (k = i; k <= j-1; k++) //在第一個與最后一個之間尋找最合適的斷開點,注意,這是從i開始,即要先計算兩個單獨矩陣相乘的乘次 { int tmp = m[i][k] + m[k+1][j] + p[i]*p[k+1]*p[j+1]; if (tmp < m[i][j]) { m[i][j] = tmp; s[i][j] = k; } } } } return m[0][n-1]; }
void print_chain(int i, int j, char **a,int **s) { //遞歸的方式來把最小乘數的表達式輸出
if (i == j) { printf("%s",a[i]); } else { printf("("); print_chain(i,s[i][j],a,s); print_chain(s[i][j]+1,j,a,s); printf(")"); } }
int main() { //min_part[i][j]存儲的是i+1到j+1的最小乘次,因為是從0開始 //min_point[i][j]存儲的是i+1到j+1之間最小乘次時的分割點 int *p, **min_part, **min_point; char **a; int n = 6,i; int ret;
p = (int *)malloc((n+1)*sizeof(int)); a = (char **)malloc(n*sizeof(char*)); min_part = (int **)malloc(n*sizeof(int *)); min_point = (int **)malloc(n*sizeof(int *));
for (i = 0; i < n; i++) { min_part[i] = (int *)malloc(n*sizeof(int)); min_point[i] = (int *)malloc(n*sizeof(int)); a[i] = (char *)malloc(n*sizeof(char)); }
p[0] = 30; //第一個矩陣的行數 p[1] = 35; //第二個矩陣的行數 p[2] = 15; //…… p[3] = 5; //…… p[4] = 10; //…… p[5] = 20; //第六個矩陣的行數 p[6] = 25; //第六個矩陣的列數
a[0] = "A1"; a[1] = "A2"; a[2] = "A3"; a[3] = "A4"; a[4] = "A5"; a[5] = "A6";
ret = matrix_chain(p,n,min_part,min_point); printf("Minest times:%d.\n",ret); print_chain(0,n-1,a,min_point); printf("\n");
free(p); free(min_part); free(min_point); free(a); system("pause");
return 0; }
3. 遞歸加括號的過程的運算量: //加括號的過程是遞歸的。 //m數組內存放矩陣鏈的行列信息 //m[i-1]和m[i]分別為第i個矩陣的行和列(i = 1、2、3...) int Best_Enum(int m[], int left, int right) { //只有一個矩陣時,返回計算次數0 if (left == right) { return 0; }
int min = INF; //無窮大 int i; //括號依次加在第1、2、3...n-1個矩陣后面 for (i = left; i < right; i++) { //計算出這種完全加括號方式的計算次數 int count = Best_Enum(m, left, i) + Best_Enum(m, i+1, right); count += m[left-1] * m[i] * m[right]; //選出最小的 if (count < min) { min = count; } } return min; }
4. 動態規划法和備忘錄優化法程序的運算量:
//動態規划法
int m[SIZE]; //存放矩陣鏈的行列信息,m[i-1]和m[i]分別為第i個矩陣的行和列(i = 1、2、3...) int d[SIZE][SIZE]; //存放矩陣鏈計算的最優值,d[i][j]為第i個矩陣到第j個矩陣的矩陣鏈的最優值,i > 0
int Best_DP(int n) { //把d[i][i]置為0,1 <= i < n memset(d, 0, sizeof(d));
int len; //遞歸計算矩陣鏈的連乘最優值 //len = 1,代表矩陣鏈由兩個矩陣構成 for (len = 1; len < n; len++) { int i, j, k; for (i = 1, j = i+len; j < n; i++, j++) { int min = INF; //無窮大 for (k = i; k < j; k++) { int count = d[i][k] + d[k+1][j] + m[i-1] * m[k] * m[j]; if (count < min) { min = count; } } d[i][j] = min; } } return d[1][n-1]; } //備忘錄優化法 int memo[SIZE][SIZE];
//m數組內存放矩陣鏈的行列信息 //m[i-1]和m[i]分別為第i個矩陣的行和列(i = 1、2、3...) int Best_Memo(int m[], int left, int right) { //只有一個矩陣時,返回計算次數0 if (left == right) { return 0; }
int min = INF; int i; //括號依次加在第1、2、3...n-1個矩陣后面 for (i = left; i < right; i++) { //計算出這種完全加括號方式的計算次數 int count; if (memo[left][i] == 0) { memo[left][i] = Best_Memo(m, left, i); } count = memo[left][i]; if (memo[i+1][right] == 0) { memo[i+1][right] = Best_Memo(m, i+1, right); } count += memo[i+1][right]; count += m[left-1] * m[i] * m[right]; //選出最小的 if (count < min) { min = count; } } return min; }
int main(void) { int n; int c; char ch; cout<<"按對應數字選擇相應方法:"<<endl; cout<<"-------------"<<endl; cout<<"1.備忘錄方法"<<endl; cout<<"2.動態規划法"<<endl; cout<<"-------------"<<endl; cout<<"請輸入數字:"; cin>>c; switch (c) { case 2: cout<<endl; cout<<"----------動態規划法----------"<<endl; while (scanf("%d", &n) != EOF) { int i; for (i = 0; i < n; i++) { scanf("%d", &m[i]); }
printf("%d\n", Best_DP(n)); cout<<"是否繼續(y/n)?"<<endl; cin>>ch; if(ch == 'n'|ch == 'N') exit(0); }; break; case 1: cout<<endl; cout<<"----------備忘錄方法----------"<<endl; while (scanf("%d", &n) != EOF) { int i; for (i = 0; i < n; i++) { scanf("%d", &m[i]); } memset(memo, 0, sizeof(memo)); printf("%d\n", Best_Memo(m, 1, n-1)); cout<<"是否繼續(y/n)?"<<endl; cin>>ch; if(ch == 'n'|ch == 'N') exit(0); }; break; } return 0; } |
程序運行結果如下:
對於矩陣連乘的標准算法,主要計算量在三重循環,總共需要pqr次數乘; 而使用動態規划算法,在計算過程中保存已解決的子問題的答案。每個子問題只計算一次,而在后面需要時只需要檢查一下,從而避免大量重復的運算。 備忘錄方法與動態規划法方法雖不同但當實驗數據一樣時運行結果相同,證明實驗中使用的兩種方法都是正確的。 綜上,矩陣連乘的最有次序計算問題可用自頂向下的備忘錄算法或自頂向上的動態規划算法在O(n3)計算時間內求解。這兩個算法都利用了子問題的重疊性質,節省了計算量,提高了算法的效率。 |