数据结构(十七)数组和矩阵
一、数组的定义:数组是n(n>=1)个相同数据类型的数据元素构成的占用一块地址连续的内存单元的有限集合。所有的线性结构(包括线性表、堆栈、队列、串、数组和矩阵)的顺序存储结构实际上就是使用数组来存储。可见,数组是其他数据结构实现存续存储结构的基础,数组这种数据结构是软件设计中最基础的数据结构。
二、数组的实现机制:数组通常以字节为计数单位,同时根据内存单元地址映像公式来分配内存。用高级语言定义数组时,数组在内存中的首地址由系统动态分配并保存。高级语言通常用数组名保存在内存中的首地址。一旦确定了一个数组的首地址,系统就可计算出该数组中任意一个数组元素的内存地址。由于计算数组各个元素内存地址的时间相等,所以存取数组中任意一个元素的时间也相等,通常称具有这种特性的存储结构为随机存储结构。所以说数组具有随机存储结构的特性。
三、在数值分析中,常常会出现一些拥有许多相同数据元素或零元素的高阶矩阵。将具有许多相同元素或者零元素,且数据分布具有一定规律的矩阵称为特殊矩阵,例如,对称矩阵、三角矩阵和对角矩阵。为了节省存储空间,需要对这类矩阵进行压缩存储。压缩存储的原则是:多个值相同的矩阵元素分配同一个存储空间,零元素不分配存储空间。对于对称矩阵、三角矩阵和对角矩阵来说,首先根据矩阵中任意一个元素与压缩后一位数组的下标的对应关系得到每一个数据元素在数组中存储的位置,然后把非零的数据元素存放到一位数组中。
四、称具有较多零元素且非零元素的分布无规律的矩阵为稀疏矩阵。由于稀疏矩阵中非零元素的分布无规律,因此,不能像特殊矩阵那样只存放非零元素值。稀疏矩阵的两种常用的存储结构是三元组表存储和十字链表存储。
1.稀疏矩阵的三元组表存储。
(1)对于稀疏矩阵中任意一个非零元素,除了存放非零元素的值(value)外,还需要同时存储它所在的行(row)、列(column)的位置;反之,用一个三元组(row,column,value)可以唯一确定一个非零元素。由此,稀疏矩阵可由表示非零元的三元组及其行列式唯一确定。
(2)稀疏矩阵的转置矩阵的三元列表存储的矩阵转置算法
以下列稀疏矩阵为例: 行下标 列下标 元素值 0 0 8 0 0 0 0 2 8 0 0 0 0 0 0 2 0 5 5 0 0 0 16 0 2 4 16 0 0 18 0 0 0 3 3 18 0 0 0 9 0 0 4 3 9 转置后为: 0 0 5 0 0 0 2 5 0 0 0 0 0 2 0 8 8 0 0 18 0 2 3 18 0 0 0 0 9 3 4 9 0 0 16 0 0 4 2 16 0 0 0 0 0
当稀疏矩阵用三元组顺序表来表示时,是以先行序、后列序的原则存放非零元素的,这样存放有利于稀疏矩阵的运算。然而,若按行列序号直接互换进行转置,则所得的三元组顺序表就不再满足先行序、后列序的原则。
为了解决此问题,可以这样来进行矩阵转置:扫描转置前的三元组,并按先列序、后行序的原则转置三元组。在原矩阵得到的三元组中,从第0行开始向下搜索列序号为0的元素,找到(2,0,5),则转置为(0,2,5),并存入转置后的三元组顺序表中。接着搜索列序号为1的元素,没找到。再搜索列号为2的元素,找到(0,2,8)并转置为(2,0,8),放入转置后的三元组顺序表中。依次类推,直到扫描完三元组,即可完成矩阵转置,并且转置后的三元组表应满足先行序、后列序的原则。
(3)求稀疏矩阵的转置矩阵的三元列表存储的矩阵快速转置算法
矩阵快速转置算法的基本思想是:假设原稀疏矩阵为N,其三元组顺序表为TN,N的转置矩阵为M,其对应的三元组顺序表为TM。先求出N的每一列的第一个非零元素在转置后的TM的行号,然后扫描转置前的TN,把该列上的元素一次存放于TM的相应位置上。由于N中第一列的第一个非零元素一定存储在TM的第一行位置上,若还知道第一列的非零元素个数,则第二列的第一个非零元素在TM中的行号就等于第一列的第一个非零元素在TM中的行号加上第一列的非零元素个数,以此类推。因为原矩阵中三元组存放顺序是先行后列,故对于同一行来说,必定先遇到列号小的元素,这样只需扫描一遍原矩阵N的TN即可。
num[i]数组表示N中第i列非零元素的个数,依次为:{1,0,2,1,1,0}
cpot[i]数组表示N中第i列的第一个非零元素在TM中的位置,即cpot[i] = cpot[i-1] +num[i-1],依次为:{0,1,1,3,4,5}
依次扫描原矩阵N的三元组顺序表TN,当扫描到一个第col列非零元素时,直接将其存放到TM的cpot[col]行号位置上,然后cpot[col]加1,即cpot[col]始终是下一个col列非零元素在TM中的行数。
也就是说,首先遍历原三元顺序表TN,得到第0个数据元素为(0,2,8),得到它的列数是2,然后根据第2列第一个数组元素在TM中的行数数组可以知道(0,2,8)这个数据元素在TN中肯定是放在第cpot[2]=1行,所以先交换行列然后把(2,0,8)放到TN的第1行,也就是data[1]的位置上,然后将cpot[2]+1=2,这是因为,如果N中同一列有两个数据元素,那么在(0,2,8)下面的那个数据元素肯定是要放在TN中的(2,0,8)的下面,根据矩阵转置的原理很好理解。然后依次类推,就可以得出新的TN顺序表了。
(4)稀疏矩阵的三元组顺序存储结构的优缺点:可以节省存储空间,并加快运算速度,但在运算过程中,若系数矩阵的非零元素位置发生变化,必将会引起数组中元素的移动,这时,对数组元素进行插入或删除操作就不太方便。针对这个问题,可以采用链式存储结构来表示稀疏矩阵,则进行插入或删除操作会更加方便一些。
2.稀疏矩阵的十字链表存储。
(1)当稀疏矩阵中非零元素的位置或个数经常发生变化时,就不宜采用三元组顺序表存储结构,而应该采用链式存储结构表示。
(2)在十字链表中稀疏矩阵的非零元素用一个结点来表示,每个结点由5个域组成,其中row域存放该非零元素所在行的位置,column域存放该非零元素所在列的位置,value域存放该非零元素的值,right域存放该非零元素同行的下一个非零元素结点指针,down域存放该非零元素同列的下一个非零元素结点指针。同一行的非零元素结点通过right域链接成一个线性链表,同一列的非零元素结点通过down域链接成一个线性链表,每个非零元素结点既是某个行链表的一个结点,又是某个列链表的某个结点,整个稀疏矩阵构成了一个十字交叉的链表,因此称这样的链表为十字链表。
五、稀疏矩阵的三元组顺序表存储结构的Java语言代码实现:
- 三元组结点类:
package bigjun.iplab.sparseMatrix; /** * 稀疏矩阵的三元组结点类 */ public class TripleNode { public int row; // 行号 public int column; // 列号 public int value; // 数据元素值 // 有参数构造方法 public TripleNode(int row, int column, int value) { this.row = row; this.column = column; this.value = value; } // 无参数构造方法 public TripleNode() { this(0, 0, 0); } }
- 实现类:
package bigjun.iplab.sparseMatrix; /** * 稀疏矩阵三元组顺序表类 */ public class SparseMatrix { public TripleNode data[]; // 三元组表 public int rows; // 行数 public int columns; // 列数 public int nums; // 非零元素个数 // 稀疏矩阵构造方法1: 为顺序表分配maxSize个存储单元并初始化 public SparseMatrix(int maxSize) { data = new TripleNode[maxSize]; for (int i = 0; i < data.length; i++) { data[i] = new TripleNode(); } rows = 0; columns = 0; nums = 0; } // 稀疏矩阵构造方法2: 根据给定的稀疏矩阵创建你三元组表 // 按先行序后列序的原则依次扫描已知稀疏矩阵的所有元素,并把非零元素插入到构造方法1生成的三元组顺序表中 public SparseMatrix(int mat[][]) { int i,j, k = 0, count = 0; rows = mat.length; // 获取行数 columns = mat[0].length; // 获取列数 for (i = 0; i < rows; i++) { // 统计非零元素的个数 for (j = 0; j < columns; j++) { if (mat[i][j] != 0) { count++; } } } nums = count; // 非零元素的个数 data = new TripleNode[nums]; // 调用构造方法1来申请三元组表的结点空间 for (i = 0; i < rows; i++) { for (j = 0; j < columns; j++) { if (mat[i][j] != 0) { data[k] = new TripleNode(i, j, mat[i][j]); // 建立三元组 k++; } } } } // 矩阵转置算法 public SparseMatrix transpose() { SparseMatrix tMatrix = new SparseMatrix(nums); // 创建矩阵对象 tMatrix.columns = rows; // 行数变为列数 tMatrix.rows = columns; // 列数变为行数 tMatrix.nums = nums; // 非零元素个数不变 int q = 0; for (int i = 0; i < columns; i++) { // 从第0行开始搜索 for (int j = 0; j < nums; j++) { // 遍历完所有元素 if (data[j].column == i) { // 第i行就找列号为i的,先0再1... tMatrix.data[q].row = data[j].column; tMatrix.data[q].column = data[j].row; tMatrix.data[q].value = data[j].value; q++; } } } return tMatrix; } // 矩阵快速转置算法 public SparseMatrix fastTranspose() { SparseMatrix tMat = new SparseMatrix(nums); // 创建矩阵对象 tMat.columns = rows; // 行数变为列数 tMat.rows = columns; // 列数变为行数 tMat.nums = nums; // 非零元素个数不变 int i, j = 0, k = 0; int[] num, cpot; if (nums > 0) { num = new int[columns]; // num[i]表示N中第i列的非零元素个数 cpot = new int[columns]; // 每列非零元素个数数组num初始化全部归零 for (i = 0; i < columns; i++) { num[i] = 0; } // 计算每列非零元素个数 for (i = 0; i < nums; i++) { j = data[i].column; num[j]++; } // 计算每列第1个非零元素在TM中的位置 cpot[0] = 0; for (i = 1; i < columns; i++) { cpot[i] = cpot[i-1] +num[i-1];//cpot[i]表示N中第i列的第一个非零元素在TM中的位置 } // 执行转置操作 for (i = 0; i < nums; i++) { // 扫描整个三元组表 j = data[i].column; k = cpot[j]; // 该元素在TM中的行号 tMat.data[k].row = data[i].column; tMat.data[k].column = data[i].row; tMat.data[k].value = data[i].value; cpot[j]++; // 该列下一个非零元素的存放位置 } } return tMat; } // 遍历稀疏矩阵并打印输出 public void MatrixTraverse() { System.out.println("稀疏矩阵的三元组存储结构: "); System.out.println("行数: " + rows + ", 列数: " + columns + ", 非零元素个数: " + nums); System.out.println("行下标 列下标 元素值"); for (int j = 0; j < nums; j++) { System.out.println(data[j].row + "\t" + data[j].column + "\t" + data[j].value); } } public static void main(String[] args) { int m[][] = { {0, 0, 8, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {5, 0, 0, 0, 16, 0}, {0, 0, 18, 0, 0, 0}, {0, 0, 0, 9, 0, 0} }; SparseMatrix sM = new SparseMatrix(m); sM.MatrixTraverse(); SparseMatrix tM = sM.transpose(); tM.MatrixTraverse(); SparseMatrix fM = sM.fastTranspose(); fM.MatrixTraverse(); } }
- 输出:
稀疏矩阵的三元组存储结构: 行数: 5, 列数: 6, 非零元素个数: 5 行下标 列下标 元素值 0 2 8 2 0 5 2 4 16 3 2 18 4 3 9 稀疏矩阵的三元组存储结构: 行数: 6, 列数: 5, 非零元素个数: 5 行下标 列下标 元素值 0 2 5 2 0 8 2 3 18 3 4 9 4 2 16 稀疏矩阵的三元组存储结构: 行数: 6, 列数: 5, 非零元素个数: 5 行下标 列下标 元素值 0 2 5 2 0 8 2 3 18 3 4 9 4 2 16