注意:这里是JAVA自学与了解的同步笔记与记录,如有问题欢迎指正说明
目录
前言:
从今天开始我们就开始图论相关内容的学习,这部分内容是编程相对复杂的多对多结构。但是今天先不急着进入,图论当中有个非常重要的存储工具——领接矩阵,所以说学会利用矩阵去完成数据的存储与表示是非常重要的,今天我们就先不急着进入重点,先试着构造个int类矩阵(我们后续的几日的讨论为方便暂时令参数为整型)以方便后续的学习。所以今天内容非常简单,关键的难度在最开始几日也介绍过了。
一、基本属性与构造函数
矩阵在计算机语言中的承载体是二维数组,自然最基本的属性就是对应类型的数组即可,当然这里你可以多定义些例如长宽等数据作为标准参数等。
/**
* The data.
*/
int[][] data;
第一个构造函数,给定长宽信息,为二维数组初始化空间。这个是基础参数的构造函数。
/**
*********************
* The first constructor.
*
* @param paraRows The number of rows.
* @param paraColumns The number of columns.
*********************
*/
public IntMatrix(int paraRows, int paraColumns) {
data = new int[paraRows][paraColumns];
}// Of the first constructor
第二个重载构造函数,是用于兼容一般二维数组的构造接口,本质上是构造个同型矩阵并且拷贝的过程。
/**
*********************
* The second constructor. Construct a copy of the given matrix.
*
* @param paraMatrix The given matrix.
*********************
*/
public IntMatrix(int[][] paraMatrix) {
data = new int[paraMatrix.length][paraMatrix[0].length];
// Copy elements.
for (int i = 0; i < data.length; i++) {
for (int j = 0; j < data[0].length; j++) {
data[i][j] = paraMatrix[i][j];
} // Of for j
} // Of for i
}// Of the second constructor
第三个重载构造函数,一个基本的同类型拷贝。在这个构造体中我们接收了来自外界的同类对象并且获取了其内部的二维数组属性(利用了getter,我们接下来再具体说说),所以本质上这个构造函数内部模仿了我们的第二个构造函数,所以这里非常灵活地在构造函数中再次通过this又使用了另一个构造函数。
使用当前对象引用this完成构造内的构造调用算是我第一次所见,以前似乎还真没遇到这种使用环境。
/**
*********************
* The third constructor. Construct a copy of the given matrix.
*
* @param paraMatrix The given matrix.
*********************
*/
public IntMatrix(IntMatrix paraMatrix) {
this(paraMatrix.getData());
}// Of the third constructor
二、getter与setter方法
getter与setter是面向对象中常用的两种方法,倒不是说类中的属性不能访问。我们类的使用中推崇私有的属性与共有的方法,然后通过方法来访问这些属性。这一方面其实能够保证属性的保密性,避免被破坏;另外一方面,Java中属性不能完成多态,只有⽅法可以多态,因此在多态时,我们可以通过多态的方法去访问子类。
当然两个说法并非绝对,第一种情况并不是所有类都需要,第二种情况的话像Python等动态类型语⾔因为没有类型系统,没有“完整”的多态特性,所以并不需要。(部分参考自互联网)
/**
*********************
* Get my data. Warning, the reference to the data instead of a copy of the data
* is returned.
*
* @return The data matrix.
*********************
*/
public int[][] getData() {
return data;
}// Of getData
/**
*********************
* Getter.
*
* @return The number of rows.
*********************
*/
public int getRows() {
return data.length;
}// Of getRows
/**
*********************
* Getter.
*
* @return The number of columns.
*********************
*/
public int getColumns() {
return data[0].length;
}// Of getColumns
/**
*********************
* Set one the value of one element.
*
* @param paraRow The row of the element.
* @param paraColumn The column of the element.
* @param paraValue The new value.
*********************
*/
public void setValue(int paraRow, int paraColumn, int paraValue) {
data[paraRow][paraColumn] = paraValue;
}// Of setValue
/**
*********************
* Get the value of one element.
*
* @param paraRow The row of the element.
* @param paraColumn The column of the element.
*********************
*/
public int getValue(int paraRow, int paraColumn) {
return data[paraRow][paraColumn];
}// Of getValue
这里的setValue与getValue方法依据的都是矩阵特定位置的值属性。
三、基本的矩阵方法
这里我们主要实现三类矩阵运算,一类是基本的矩阵加法运算。这里我们先实现了外来矩阵对于当前类矩阵的加法运算:
/**
*********************
* Add another matrix to me.
*
* @param paraMatrix The other matrix.
*********************
*/
public void add(IntMatrix paraMatrix) throws Exception {
// Step 1. Get the data of the given matrix.
int[][] tempData = paraMatrix.getData();
// Step 2. Size check.
if (data.length != tempData.length) {
throw new Exception(
"Cannot add matrices. Rows not match: " + data.length + " vs. " + tempData.length + ".");
} // Of if
if (data[0].length != tempData[0].length) {
throw new Exception(
"Cannot add matrices. Rows not match: " + data[0].length + " vs. " + tempData[0].length + ".");
} // Of if
// Step 3. Add to me.
for (int i = 0; i < data.length; i++) {
for (int j = 0; j < data[0].length; j++) {
data[i][j] += tempData[i][j];
} // Of for j
} // Of for i
}// Of add
加法的过程我不详讲了,具体可见我第七日的代码过程,这里我们使用了自定义的异常抛出功能而没有采用常规的打印语句。其实,对于大型的项目,抛出异常是一种常见的处理手段,因为寻常的打印语句报错具有一定主观性,计算机并不能认为其是异常,而是认为构造的一共自主异常。而抛出异常后可以通过try等操作更明显地分割异常处理与正常处理。
完成这个自加加法后,进一步可以设计出通用性的返回矩阵的加法(两矩阵相加返回一个矩阵,可以简化为单独创建一个矩阵然后基于这个矩阵用刚刚使用的自加方案):
/**
*********************
* Add two existing matrices.
*
* @param paraMatrix1 The first matrix.
* @param paraMatrix2 The second matrix.
* @return A new matrix.
*********************
*/
public static IntMatrix add(IntMatrix paraMatrix1, IntMatrix paraMatrix2) throws Exception {
// Step 1. Clone the first matrix.
IntMatrix resultMatrix = new IntMatrix(paraMatrix1);
// Step 2. Add the second one.
resultMatrix.add(paraMatrix2);
return resultMatrix;
}// Of add
第二类是基本的矩阵乘法运算。这部分内容的代码实现过程我在自己第8天的博客中做了比较清晰的说明,这里也不赘述。
/**
*********************
* Multiply two existing matrices.
*
* @param paraMatrix1 The first matrix.
* @param paraMatrix2 The second matrix.
* @return A new matrix.
*********************
*/
public static IntMatrix multiply(IntMatrix paraMatrix1, IntMatrix paraMatrix2) throws Exception {
// Step 1. Check size.
int[][] tempData1 = paraMatrix1.getData();
int[][] tempData2 = paraMatrix2.getData();
if (tempData1[0].length != tempData2.length) {
throw new Exception("Cannot multiply matrices: " + tempData1[0].length + " vs. " + tempData2.length + ".");
} // Of if
// Step 2. Allocate space.
int[][] resultData = new int[tempData1.length][tempData2[0].length];
// Step 3. Multiply.
for (int i = 0; i < tempData1.length; i++) {
for (int j = 0; j < tempData2[0].length; j++) {
for (int k = 0; k < tempData1[0].length; k++) {
resultData[i][j] += tempData1[i][k] * tempData2[k][j];
} // Of for k
} // Of for j
} // Of for i
// Step 4. Construct the matrix object.
IntMatrix resultMatrix = new IntMatrix(resultData);
return resultMatrix;
}// Of multiply
最后,我们额外添加了一些线代当中基本运算方法——生成单位矩阵。这个操作并不算难,因为单位阵是个方阵,所以我们只要给定一个值——阶数,即可生成对应二维空间(默认初始为0),然后按照行列相同的索引逐步赋值为1以满足单位阵之特性。
/**
*********************
* Get identity matrix. The values at the diagonal are all 1.
*
* @param paraRows The given rows.
*********************
*/
public static IntMatrix getIdentityMatrix(int paraRows) {
IntMatrix resultMatrix = new IntMatrix(paraRows, paraRows);
for (int i = 0; i < paraRows; i++) {
// According to access control, resultMatrix.data can be visited
// directly.
resultMatrix.data[i][i] = 1;
} // Of for i
return resultMatrix;
}// Of getIdentityMatrix
四、数据测试
了解每个方法的含义,下面的说明基本没什么压力了:
IntMatrix tempMatrix1 = new IntMatrix(3, 3);
tempMatrix1.setValue(0, 1, 1);
tempMatrix1.setValue(1, 0, 1);
tempMatrix1.setValue(1, 2, 1);
tempMatrix1.setValue(2, 1, 1);
System.out.println("The original matrix is: " + tempMatrix1);
这里我们构造了一个3阶的方阵,然后逐个对其进行了赋值(简易图如下):
这里我们的打印方案是重写了toString方案,采用的方法是Arrays.deepToString方法。
下面是加法和乘法的具体演示:
IntMatrix tempMatrix2 = null;
try {
tempMatrix2 = IntMatrix.multiply(tempMatrix1, tempMatrix1);
} catch (Exception ee) {
System.out.println(ee);
} // Of try
System.out.println("The square matrix is: " + tempMatrix2);
IntMatrix tempMatrix3 = new IntMatrix(tempMatrix2);
try {
tempMatrix3.add(tempMatrix1);
} catch (Exception ee) {
System.out.println(ee);
} // Of try
System.out.println("The connectivity matrix is: " + tempMatrix3);
总结
今天的内容是热身,具体来说就是建立一个通用性的矩阵类,为后续图相关数据结构学习准备一种关键的存储结构:邻接矩阵。我们在使用顺序表的时候都存在一种基于下标建立索引的用法,将顺序表结构的[下标->内容]的O(1)级的查询效率利用起来构造一个类似于建议哈希的结构。比如前几日在建立Huffman树的时候就使用boolean类型的tempProcessed数组来实现“结点是否被访问”这样的查询,另外在排序算法中,又有例如桶排序这种经典的案例。
这种思维扩展到二维,最常用的运用就是邻接矩阵。将矩阵的一维与二维方向的i, j作为我们需要查询的两个结点的编号,进行随机存取,查询得到关于这两个结点之间信息关系的信息。这个过程,算上查询结果,一共我们处理了三个信息,刚好这满足图中的两结点和结点之间的边权的组合。因此,自然而然地,我们的计算机先辈们就将其作为了图在计算机存储中的表示。
当然领接矩阵并不是唯一的存储图的方案,但是绝对是最好理解的利用的方案,更进一步也许我们可以采用邻接表、十字链表、三元组表(或者用哈希存储),但是在这之前,最先接触和了解的一定是二维邻接矩阵,这是认识图的开始。