以下是本节涉及的知识点:
本节跟前面两节相比,量不大,但是会比较难,因为矩阵乘法涉及到三重循环,难在我们是如何分析得到这三重循环以及这三重循环怎么写。同时还涉及到二维数组,如果稍有不注意很可能会晕。
我们还是从一个基本的情景开始:
(佟强作业题改)初始化两个二维矩阵A和B,其中A是一个M*N的矩阵,B是N*P的矩阵(其中M、N、P均为某个常数,我们假定M=3,N=4,P=2),矩阵里面的数字均为整数,计算矩阵C=AB,并输出矩阵C。
佟强作业题原题会比现在这个版本简单,但是原题还涉及到数组指针和函数,在这里都省略了。
首先我们来补充一下基本的矩阵乘法的知识:
二维矩阵:一个数表,M*N
的矩阵表示一个M行N列的数阵
,其中有M*N
个数。
矩阵乘法:两个矩阵的乘法为:当第一个矩阵的列数等于第二个矩阵的行数时才能进行。对于题目中的A
B
矩阵来说,刚好满足这个关系,则乘积为C
,C
为一个M*P
的矩阵。
C
中的第i行第j列元素
可以表示为:
c i j = ∑ k = 1 N a i k b k j c_{ij}=\sum_{k=1}^N a_{ik} b_{kj} cij=k=1∑Naikbkj
即C
中的第i行第j列元素
为A
矩阵i行
与B
矩阵j列
的每个对应元素乘积之和。
比如:
C = A B = ( 1 2 3 4 5 6 ) ( 1 4 2 5 3 6 ) = ( 1 × 1 + 2 × 2 + 3 × 3 1 × 4 + 2 × 5 + 3 × 6 4 × 1 + 5 × 2 + 6 × 3 4 × 4 + 5 × 5 + 6 × 6 ) = ( 14 32 32 77 ) C=AB=\begin{pmatrix}1 & 2 & 3\\4 & 5 & 6\end{pmatrix}\begin{pmatrix}1 & 4 \\ 2 & 5 \\ 3 & 6 \end{pmatrix}=\begin{pmatrix}1×1+2×2+3×3 & 1×4+2×5+3×6 \\ 4×1+5×2+6×3 & 4×4+5×5+6×6\end{pmatrix}=\begin{pmatrix}14 & 32\\32 &77\end{pmatrix} C=AB=(142536)⎝⎛123456⎠⎞=(1×1+2×2+3×34×1+5×2+6×31×4+2×5+3×64×4+5×5+6×6)=(14323277)
接下来我们要实现矩阵乘法。还是先把上述情景进行步骤分解:
A
B
C
C
中某一元素的值C
中某行的每个元素求值C
中的每行都进行上述操作C
首先要干的事情是定义矩阵。矩阵我们可以用二维数组来进行存储(虽然也可以用一维数组,但是二维显然更方便)。我们可以利用随机数初始化这个矩阵,也可以让用户输入一个矩阵,但这都是前两节细讲过的东西,在这里我们简单来就好,直接在代码里面对数组进行初始化。
#include
using namespace std;
int main(){
const int M=3;
const int N=4;
const int P=2;
int A[M][N]={
1, 8, 5, 4,
2, 6, 3, 8,
7, 2, 1, 9
};
int B[N][P]={
1, 0,
7, 4,
9, 3,
1, 5
};
int C[M][P];
}
如上,我们还是先把常量M
、N
、P
定义了,这样万一今后需要修改矩阵大小,从这里修改就可以了。A
和B
我们使用数组初始化的方式直接初始化。为了让矩阵的形状更加清晰,我在这里把数字按矩阵本身的形状排列了。在C++里面,空格和缩进多数情况下不会影响程序运行,所以我们做到美观即可。矩阵内容是我随便输的。
矩阵乘法听起来很复杂,但也是有规律可循的。我们从一个最基本的情况做起:计算矩阵C第i行第j列元素
的值。由前面的公式可知,矩阵C第i行第j列元素
的值为,矩阵A第i行第1列元素
与B的第1行第j列元素
乘积,加上矩阵A第i行第2列元素
与B的第2行第j列元素
乘积,一直加到矩阵A第i行最后一列元素
与B的最后一行第j列元素
乘积。可以初步看出这是一个循环。
矩阵A第i行第1列元素
与B的第1行第j列元素
的乘积我们可以这么写:
A[i][1]*B[1][j]
那么做到循环里面的时候,我们计算矩阵A第i行第k列元素
与B的第k行第j列元素
的乘积就这么写:
A[i][k]*B[k][j]
这里面一共有N
个这样的乘积,所以我们把这些乘积累加起来。这需要用到知识网络1.1
里面介绍的求和的模式:
int sum=0;
for(int k=0;k<N;k++){
sum+=A[i][k]*B[k][j];
}
求和之后,就很简单了,把这个和填到矩阵C第i行第j列元素
里面即可:
C[i][j]=sum;
我们现在会求一个元素的值了,那么求其余所有元素的值也是如法炮制。我们先试着把矩阵C第i行所有元素
的值都求出来。也就是说此时前面提到的j
就会从0
一直到P-1
进行循环。大概逻辑是这样的:
for(int j=0; j
把中间的文字替换成前面写出的代码,就有了如下的二重for
循环:
for(int j=0; j<P; j++){
int sum=0;
for(int k=0;k<N;k++){
sum+=A[i][k]*B[k][j];
}
C[i][j]=sum;
}
这段代码便能够为矩阵C的第i行整行
求值了。
最后一步也是一样的了,我们既然能算一整行,那么只要对于矩阵C的每一行,都执行上述操作即可。大概逻辑是这样的:
for(int i=0; i
把文字替换为前面写出的代码,就有了如下怪物一样的三重for
循环:
for(int i=0; i<M; i++){
for(int j=0; j<P; j++){
int sum=0;
for(int k=0;k<N;k++){
sum+=A[i][k]*B[k][j];
}
C[i][j]=sum;
}
}
最后一步是输出。由于是二维数组,所以我们也需要二重循环,逐行逐列对矩阵C进行输出。当然具体的输出格式按自己的喜好即可:
for(int i=0; i<M; i++){
for(int j=0; j<P; j++){
cout<<C[i][j]<<'\t';
}
cout<<endl;
}
当然到这里就可以结束了。但是我还想再提一点之前都没重视的地方:我们所有的临时变量(i
、j
、k
、sum
)都是在需要用的时候才被定义。但实际上,我们也可以在程序一开始的时候就把它们定义好。这样子后面写的时候,就不用再在for
循环里面定义了,能省不少麻烦。
比如:
for(int i=0; i<M; i++){
for(int j=0; j<P; j++){
int sum=0;
for(int k=0;k<N;k++){
sum+=A[i][k]*B[k][j];
}
C[i][j]=sum;
}
}
就可以替换为:
int i, j, k, sum;
for(i=0; i<M; i++){
for(j=0; j<P; j++){
sum=0;
for(k=0;k<N;k++){
sum+=A[i][k]*B[k][j];
}
C[i][j]=sum;
}
}
总结一下,我们现在总共写了些什么:
#include
using namespace std;
int main(){
//定义常量
const int M=3;
const int N=4;
const int P=2;
//定义矩阵并初始化
int A[M][N]={
1, 8, 5, 4,
2, 6, 3, 8,
7, 2, 1, 9
};
int B[N][P]={
1, 0,
7, 4,
9, 3,
1, 5
};
int C[M][P];
int i, j, k, sum;
//对于矩阵C的每一行
for(i=0; i<M; i++){
//对于矩阵C的第i行的每个元素
for(j=0; j<P; j++){
//对于矩阵C的第i行第j列的元素进行求值
sum=0;
//计算矩阵A第i行每个元素与矩阵B第j列每个元素的对应乘积之和
for(k=0;k<N;k++){
sum+=A[i][k]*B[k][j];
}
//把这个乘积之和填到矩阵C的第i行第j列的元素里面
C[i][j]=sum;
}
}
//对于矩阵C的每一行
for(i=0; i<M; i++){
//对于矩阵C的第i行的每个元素
for(j=0; j<P; j++){
//输出矩阵C的第i行第j列的元素
cout<<C[i][j]<<'\t';
}
cout<<endl;//当一行输出结束时换行
}
}
参见附件知识网络1.3.1
,运行一下看看结果。
很幸运,本节没有更多的幺蛾子了,这一节就到这里!