# 矩阵相乘
## 题目描述
请编程实现矩阵乘法,并考虑当矩阵规模较大时的优化方法。
## 分析与解法
根据wikipedia上的介绍:两个矩阵的乘法仅当第一个矩阵A的行数和另一个矩阵B的列数相等时才能定义。如A是m×n矩阵,B是n×p矩阵,它们的乘积AB是一个m×p矩阵,它的一个元素其中 1 ≤ i ≤ m, 1 ≤ j ≤ p。
![](../images/41~42/42.1.png)
值得一提的是,矩阵乘法满足结合律和分配率,但并不满足交换律,如下图所示的这个例子,两个矩阵交换相乘后,结果变了:
![](../images/41~42/42.1-2.png)
下面咱们来具体解决这个矩阵相乘的问题。
### 解法一、暴力解法
其实,通过前面的分析,我们已经很明显的看出,两个具有相同维数的矩阵相乘,其复杂度为O(n^3),参考代码如下:
```cpp
//矩阵乘法,3个for循环搞定
void MulMatrix(int** matrixA, int** matrixB, int** matrixC)
{
for(int i = 0; i < 2; ++i)
{
for(int j = 0; j < 2; ++j)
{
matrixC[i][j] = 0;
for(int k = 0; k < 2; ++k)
{
matrixC[i][j] += matrixA[i][k] * matrixB[k][j];
}
}
}
}
```
### 解法二、Strassen算法
在解法一中,我们用了3个for循环搞定矩阵乘法,但当两个矩阵的维度变得很大时,O(n^3)的时间复杂度将会变得很大,于是,我们需要找到一种更优的解法。
一般说来,当数据量一大时,我们往往会把大的数据分割成小的数据,各个分别处理。遵此思路,如果丢给我们一个很大的两个矩阵呢,是否可以考虑分治的方法循序渐进处理各个小矩阵的相乘,因为我们知道一个矩阵是可以分成更多小的矩阵的。
如下图,当给定一个两个二维矩阵A B时:
![](../images/41~42/42.2.png)
这两个矩阵A B相乘时,我们发现在相乘的过程中,有8次乘法运算,4次加法运算:
![](../images/41~42/42.3.png)
矩阵乘法的复杂度主要就是体现在相乘上,而多一两次的加法并不会让复杂度上升太多。故此,我们思考,是否可以让矩阵乘法的运算过程中乘法的运算次数减少,从而达到降低矩阵乘法的复杂度呢?答案是肯定的。
1969年,德国的一位数学家Strassen证明O(N^3)的解法并不是矩阵乘法的最优算法,他做了一系列工作使得最终的时间复杂度降低到了O(n^2.80)。
他是怎么做到的呢?还是用上文A B两个矩阵相乘的例子,他定义了7个变量:
![](../images/41~42/42.4.png)
如此,Strassen算法的流程如下:
* 两个矩阵A B相乘时,将A, B, C分成相等大小的方块矩阵:
![](../images/41~42/42.5.png)
* 可以看出C是这么得来的:
![](../images/41~42/42.6.jpeg)
* 现在定义7个新矩阵(*读者可以思考下,这7个新矩阵是如何想到的*):
![](../images/41~42/42.7.jpeg)
* 而最后的结果矩阵C 可以通过组合上述7个新矩阵得到:
![](../images/41~42/42.8.jpeg)
表面上看,Strassen算法仅仅比通用矩阵相乘算法好一点,因为通用矩阵相乘算法时间复杂度是![equation](http://latex.codecogs.com/gif.latex?{n^3=n^{\log_28}}),而Strassen算法复杂度只是
![equation](http://latex.codecogs.com/gif.latex?{O(n^{\log_27})=O(n^{2.807})})。但随着n的变大,比如当n >> 100时,Strassen算法是比通用矩阵相乘算法变得更有效率。
如下图所示:
![](../images/41~42/42.9.png)
根据wikipedia上的介绍,后来,Coppersmith–Winograd 算法把 N* N大小的矩阵乘法的时间复杂度降低到了:![equation](http://latex.codecogs.com/gif.latex?{O(n^{2.375477})}),而2010年,Andrew Stothers再度把复杂度降低到了![equation](http://latex.codecogs.com/gif.latex?{O(n^{2.3736})}),一年后的2011年,Virginia Williams把复杂度最终定格为:![equation](http://latex.codecogs.com/gif.latex?{O(n^{2.3727})})。
- 程序员如何准备面试中的算法
- 第一部分 数据结构
- 第一章 字符串
- 1.0 本章导读
- 1.1 旋转字符串
- 1.2 字符串包含
- 1.3 字符串转换成整数
- 1.4 回文判断
- 1.5 最长回文子串
- 1.6 字符串的全排列
- 1.10 本章习题
- 第二章 数组
- 2.0 本章导读
- 2.1 寻找最小的 k 个数
- 2.2 寻找和为定值的两个数
- 2.3 寻找和为定值的多个数
- 2.4 最大连续子数组和
- 2.5 跳台阶
- 2.6 奇偶排序
- 2.7 荷兰国旗
- 2.8 矩阵相乘
- 2.9 完美洗牌
- 2.15 本章习题
- 第三章 树
- 3.0 本章导读
- 3.1 红黑树
- 3.2 B树
- 3.3 最近公共祖先LCA
- 3.10 本章习题
- 第二部分 算法心得
- 第四章 查找匹配
- 4.1 有序数组的查找
- 4.2 行列递增矩阵的查找
- 4.3 出现次数超过一半的数字
- 第五章 动态规划
- 5.0 本章导读
- 5.1 最大连续乘积子串
- 5.2 字符串编辑距离
- 5.3 格子取数
- 5.4 交替字符串
- 5.10 本章习题
- 第三部分 综合演练
- 第六章 海量数据处理
- 6.0 本章导读
- 6.1 关联式容器
- 6.2 分而治之
- 6.3 simhash算法
- 6.4 外排序
- 6.5 MapReduce
- 6.6 多层划分
- 6.7 Bitmap
- 6.8 Bloom filter
- 6.9 Trie树
- 6.10 数据库
- 6.11 倒排索引
- 6.15 本章习题
- 第七章 机器学习
- 7.1 K 近邻算法
- 7.2 支持向量机
- 附录 更多题型
- 附录A 语言基础
- 附录B 概率统计
- 附录C 智力逻辑
- 附录D 系统设计
- 附录E 操作系统
- 附录F 网络协议
- sift算法
- sift算法的编译与实现
- 教你一步一步用c语言实现sift算法、上
- 教你一步一步用c语言实现sift算法、下
- 其它
- 40亿个数中快速查找
- hash表算法
- 一致性哈希算法
- 倒排索引关键词不重复Hash编码
- 傅里叶变换算法、上
- 傅里叶变换算法、下
- 后缀树
- 基于给定的文档生成倒排索引的编码与实践
- 搜索关键词智能提示suggestion
- 最小操作数
- 最短摘要的生成
- 最长公共子序列
- 木块砌墙原稿
- 附近地点搜索
- 随机取出其中之一元素