矩阵连乘问题
若矩阵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数组都是用来记录的,所以备忘录方法与我们使用的方法类似,不同在于,我们是自底向上的,而备忘录方法是自顶向下的进行。