日撸 Java 三百行(08天,矩阵乘法的代码实现)
注意:这里是JAVA自学与了解的同步笔记与记录,如有问题欢迎指正说明
一、矩阵乘法的内涵
先需要对矩阵乘法有个基本的认识,虽然本科阶段可能在线代中已经了解过这个非常基础的内容,但是今天为了代码实现,不妨再啰嗦几句。
所谓矩阵乘法是两个具有一定关系的两个矩阵之间彼此行列之间关系的一种运算,一般来说,左乘矩阵的列数等于右乘矩阵的行数。然后运算时候左矩阵逐行提取分别与右乘矩阵的逐列相乘再相加。口述空洞,我们用图的表达式来说明:
这里我们定义A矩阵为m×n的矩阵,B矩阵为n×p的一个矩阵,自然C是个m×p的矩阵。对于C矩阵中的每个元素的计算可有以下定义:
每个单独C中的元素都是由矩阵A和矩阵B中确定的一个行与列作用得到的。
就图中的C[1][1]按照这种理论可以写作:
C[1][1] = A[1][0]*B[0][1] + A[1][1]*B[1][1] + A[1][2]*B[2][1] + A[1][3]*B[3][1] + A[1][4]*B[4][1]
由此可见,A矩阵的第i行可以与B矩阵的第j列共同决定C矩阵的(i,j)元素,如下图所示(来自互联网):
二、代码逻辑
· 开销分析
因为A矩阵的全体行构造的集合要与B矩阵全体列构成的集合进行逐一运算,因此自然匹配开销是O(n2)。而且运算时是若干乘积求和,这个乘积的过程在代码也需要一个循环来实现,所以直观来看我们需要三层循环来完成程序,基本复杂度是O(n3)
· 核心代码
上述分析后基本确定核心代码就是三层for循环,但是思维上要注意的难点就是捋清楚循环的上限以及行列区分。
目前假设A矩阵是m×n的,B矩阵是n×p的
通过for (int i = 0; i < m; i++) { A[i][...] }
取得A的逐行
通过for (int j = 0; i < p; i++) { B[...][j] }
取得B的逐列
这些行列的乘积要求n次和,于是再通过for (int k = 0; k < n; k++) { }
完成求和
综上完成核心代码:
int[][] resultMatrix = new int[m][p];
for (int i = 0; i < m; i++) {
for (int j = 0; j < p; j++) {
for (int k = 0; k < n; k++) {
resultMatrix[i][j] += paraFirstMatrix[i][k] * paraSecondMatrix[k][j];
} // Of for k
} // Of for j
} // Of for i
三、数据测试
本次将测试任意两个矩阵的乘法与矩阵自乘的操作。当然因为矩阵乘法具有一定行列限制,所以在我们构造的函数中需要增加提升函数健壮性的非法案例的判别。
代码如下:
package basic;
import java.util.Arrays;
/**
* This is the eighth code. Names and comments should follow my style strictly.
*
* @author Xingyi Zhang 1328365276@qq.com
*/
public class MatrixMultiplication {
/**
*********************
* The entrance of the program.
*
* @param args Not used now.
*********************
*/
public static void main(String args[]) {
matrixMultiplicationTest();
}// Of main
/**
*********************
* Matrix multiplication. The columns of the first matrix should be equal to the
* rows of the second one.
*
* @param paraFirstMatrix The first matrix.
* @param paraSecondMatrix The second matrix.
* @return The result matrix.
*********************
*/
public static int[][] multiplication(int[][] paraFirstMatrix, int[][] paraSecondMatrix) {
int m = paraFirstMatrix.length;
int n = paraFirstMatrix[0].length;
int p = paraSecondMatrix[0].length;
// Step 1. Dimension check.
if (paraSecondMatrix.length != n) {
System.out.println("The two matrices cannot be multiplied.");
return null;
} // Of if
// Step 2. The loop.
int[][] resultMatrix = new int[m][p];
for (int i = 0; i < m; i++) {
for (int j = 0; j < p; j++) {
for (int k = 0; k < n; k++) {
resultMatrix[i][j] += paraFirstMatrix[i][k] * paraSecondMatrix[k][j];
} // Of for k
} // Of for j
} // Of for i
return resultMatrix;
}// Of multiplication
/**
*********************
* Unit test for respective method.
*********************
*/
public static void matrixMultiplicationTest() {
int[][] tempFirstMatrix = new int[2][3];
for (int i = 0; i < tempFirstMatrix.length; i++) {
for (int j = 0; j < tempFirstMatrix[0].length; j++) {
tempFirstMatrix[i][j] = i + j;
} // Of for j
} // Of for i
System.out.println("The first matrix is: \r\n" + Arrays.deepToString(tempFirstMatrix));
int[][] tempSecondMatrix = new int[3][2];
for (int i = 0; i < tempSecondMatrix.length; i++) {
for (int j = 0; j < tempSecondMatrix[0].length; j++) {
tempSecondMatrix[i][j] = i + j;
} // Of for j
} // Of for i
System.out.println("The second matrix is: \r\n" + Arrays.deepToString(tempSecondMatrix));
int[][] tempThirdMatrix = multiplication(tempFirstMatrix, tempSecondMatrix);
System.out.println("The third matrix is: \r\n" + Arrays.deepToString(tempThirdMatrix));
System.out.println("Trying to multiply the first matrix with itself.\r\n");
tempThirdMatrix = multiplication(tempFirstMatrix, tempFirstMatrix);
System.out.println("The result matrix is: \r\n" + Arrays.deepToString(tempThirdMatrix));
}// Of matrixMultiplicationTest
}// Of matrixMultiplication
得到显示如下:
最开始构造两个矩阵相乘得到预期结果,但是矩阵1因为自身并不是方阵,因此无法自乘,故抛出了错误提示。
总结与感想
矩阵的操作是非常丰富的,计算机的后续学习中我们仍然会在很多地方看见矩阵,但是介于本篇主要是个人用于学习java的基本笔记记录,就不过多深入纠结矩阵的各种其他应用了。
相比于昨天的基础矩阵应用,今天矩阵乘法难度上稍微提升了一部分,也许在我们自己手算矩阵乘法时,因为矩阵的形象是直观的,两个矩阵之间的位置关系也一目了然,利用人基本的空间能力,我们并不会认为这是个非常难的过程。
但是实际应用在代码模拟时,却会陷入“ 我的for循环到底怎么循环?循环多少次?这个for循环怎么迭代?现在是行还是列?” 的诸多疑问中。
因此,要正确把数学问题与代码问题联系起来,我们就需要深入把平时觉得简单的内容解构,用数学的方法去给问题去建模,去分析。例如根据C矩阵每个元素的来源从而确定是哪一行哪一列;矩阵乘法算式构造去解析有几成循环从而确定for循环迭代方案以及复杂度;再或者随便举一个案例,通过案例的特殊性,从特殊到一般地去重构代码。
总之,要真正用代码去解决一个实际问题,光有一个灵活的数学思维头脑是不够的的,还要有利用代码去模拟数学方案的能力,自然,如果你只有完美的代码思维,如果数学问题都不知道怎么解,那就属于是一个懂得各种交通规则的人却连车都不知道怎么发动。
【例如俺最头疼的算法中的动态规划算法(DP: Dynamic programming),只要正确建立一个动态规划的状态转化的数学模型,那编程就很轻松了,然而这个真的…很不好建】