前言
从旭东的博客 看到一篇博文:矩阵连乘最优结合 动态规划求解,挺有意思的,这里做个转载【略改动】。
问题
矩阵乘法满足结合律,但不满足交换律。例如矩阵$A_{ab}, B_{bc}, C_{cd}$ 连乘得到矩阵$S_{ad}$
\[ S_{ad}=A_{ab} B_{bc} C_{cd} \]
实际运算可以先将矩阵A和B相乘,S=(AB)C,也可以先将矩阵B和C相乘,S=A(BC) 。这两种算法的计算量是否一样呢?
先看A×B的计算量。A×B是a行c列矩阵,共a×c个元素。b是A、B两矩阵的乘法接口,积矩阵的每个元素都需经过b次乘法运算。那么A×B需要a×b×c次乘法运算。
(AB)C乘法运算次数为:abc+acd=ac(b+d)
A(BC)乘法运算次数为:bcd+abd=bd(a+c)
由于a、b、c、d的取值是任意的, 所以ac(b+d)和bd(a+c)不可能保持相同,也即结合律可以用于矩阵乘法优化。那么,对于连乘AiAi+1…Aj,如何找出最优的运算顺序?
分析
这个问题长得像递归,但是用递归似乎不好做,因为子问题里面有很多分支,那子问题的返回值是什么,你返回哪个分支的返回值?而且就算能实现,肯定会对一些相同的子问题重复计算。
由于计算n个矩阵的连乘,总是通过计算更少个数的矩阵连乘实现的,最终化归到计算两个矩阵的相乘,所以考虑从两个矩阵相乘着手。这样自底向上,就把问题解决了。当我们把所有的两个矩阵相乘的乘法运算次数求出后,便可以去计算三个矩阵相乘的乘法运算次数。这时,三个矩阵的连乘相当于两个矩阵相乘。这样,所有的矩阵连乘都是两个矩阵相乘,其乘法运算次数为两个矩阵各自的乘法运算次数之和再加上两个矩阵相乘的乘法运算次数。对于两个矩阵A、B相乘的乘法运算次数$\beta$,上面A×B的示例已经讲过了。进一步可以抽象为
\[\beta=行*接口*列\]
C++提供了vector模板类,作为数组的一个替代品,我们用vector实现n阶方阵。首先确定跨度,再确定起点,构成一个二级嵌套循环,通过起点的平移确定子式。再嵌套一级遍历子式的分割点求出子式的最少计算次数。最后我们得到最大的子式,也就是连乘本身的最少运算次数及分割方案。
代码如下:

1 #include <iostream> 2 #include <vector> 3 #include <algorithm> 4 #include <limits.h> 5 #include <string> 6 7 using namespace std; 8 9 //计算矩阵的分割方案(基于向量) 10 int calculate_M(vector<vector<int> >&num, vector<pair<int, int> > &data, vector<vector<int> > &points) { 11 int matrixNum = data.size(); 12 int span;//起止点间隔距离,表示跨度 13 int start;//起点 14 int end;//终点 15 int spiltPoint;//分割点 16 int multiplyTimes;//临时存储子式的乘法运算次数 17 for (span = 1; span < matrixNum; span++) { ///间隔距离 相邻矩阵的间隔距离为1 18 for (start = 0; start < matrixNum - span; start++) { ///起点平移 19 //子式确立,下面开始计算这个子式的最少乘法运算次数,初值先设为最大整数 20 end = start + span; 21 num[start][end] = INT_MAX; 22 for (spiltPoint = start; spiltPoint < end; spiltPoint++) { 23 24 multiplyTimes = num[start][spiltPoint] + num[spiltPoint + 1][end] + data[start].first*data[spiltPoint].second*data[end].second; 25 if (multiplyTimes < num[start][end]) { 26 points[start][end] = spiltPoint; ///记录分割点 27 num[start][end] = multiplyTimes; ///记录最少乘法次数 28 } 29 } 30 } 31 } 32 return 0; 33 } 34 35 //根据记录的分割点,生成最后的矩阵相乘表达式 36 string make_result(vector<vector<int> > &points, int t1, int t2) { 37 if (t1 == t2) 38 return string(1, 'A' + t1); 39 int split = points[t1][t2]; 40 return "(" + make_result(points, t1, split) + "*" + make_result(points, split + 1, t2) + ")"; 41 } 42 43 44 int main() 45 { 46 int matrixNum=0;///连乘的矩阵个数 47 vector<pair<int, int>> data; ///存储矩阵的尺寸 48 49 //固定输入 50 data.push_back(make_pair(10, 100)); //A 51 data.push_back(make_pair(100, 5)); //B 52 data.push_back(make_pair(5, 25)); //C 53 data.push_back(make_pair(25, 15)); //D 54 data.push_back(make_pair(15, 20)); //E 55 matrixNum = 5; 56 57 //为记录乘法次数和分割点的n阶矩阵分配空间并初始化 58 vector<vector<int> > num(matrixNum, vector<int>(matrixNum)); 59 vector<vector<int> > points(matrixNum, vector<int>(matrixNum));