稀疏矩阵
稀疏矩阵的压缩存储
-
稀疏矩阵的概念
具有较多零元素且非零元素的分布无规律的矩阵为稀疏矩阵
稀疏矩阵的三元组表存储
-
存储方式
所有非零元素组成一个三元组表,按行优先顺序,将稀疏矩阵中的非零元素存放在一个有三元组组成的数组中
例:
5×6的稀疏矩阵如下图所示:
其对应的三元组存储表如下图所示:
三元组表的基本操作
-
定义稀疏矩阵的三元组结点类
定义属性:行号、列号、值
定义构造方法:有参、无参
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); } } `
-
定义稀疏矩阵的三元组表类
定义属性:三元组结点类型的数组、行数、列数、非0元素个数
定义构造方法:
(1)为三元组表分配maxsize个存储单元、初始化行数、列数、非0元素个数
(2)从一个稀疏矩阵创建三元组表:按先行序的原则依次扫描稀疏矩阵的所有元素,把非0元素插入到三元组表中
//稀疏矩阵三元组表类 public class SparseMatrix { public TripleNode[] data; public int rows; //行数 public int cols; //列数 public int nums; //非0元素个数 public SparseMatrix(int maxSize) { data = new TripleNode[maxSize]; for(int i= 0;i<data.length;i++) { //为顺序表分配存储单元 data[i] = new TripleNode(); } rows = 0; cols = 0; nums = 0; } //从一个稀疏矩阵创建三元组表 public SparseMatrix(int mat[][]) { int count = 0; int k = 0; rows = mat.length; cols = mat[0].length; //遍历二维数组,统计非0元素个数 for(int i = 0;i<mat.length;i++) { for(int j = 0;j<mat[i].length;j++) { if(mat[i][j] != 0) { count++; } } } nums = count; //申请三元组空间 data = new TripleNode[nums]; //把非0元素存到三元组数组中 for(int i = 0;i<mat.length;i++) { for(int j = 0;j<mat[i].length;j++) { if(mat[i][j] != 0) { data[k] = new TripleNode(i,j,mat[i][j]); k++; } } } } //打印输出稀疏矩阵 public void printMatrix() { System.out.println("稀疏矩阵的三元组存储结构:"); System.out.println("行数:" + rows +",列数:" + cols + ",非0元素个数:" + nums); System.out.println("行下标" + "\t" + "列下标" + "\t" + "非0元素"); for(int i = 0;i < nums; i++) { System.out.println(data[i].row + "\t" + data[i].column + "\t" + data[i].value); } }
矩阵的普通转置与快速转置
矩阵转置:将矩阵中的每一个行列号互换,使一个m×n的矩阵变成一个n×m的矩阵。
-
普通转置
思路:在原来的三元组存放的列序号中,从头开始扫描列号0进行行列交换,找到则存放在一个新的三元组表数组的第0位置中,否则,继续扫描列号1进行行列交换,找到则存放在新三元组表数组的第1位置。以此类推…
例:
代码:
//普通转置 public SparseMatrix transpose() { SparseMatrix tm = new SparseMatrix(nums); tm.cols = rows; tm.rows = cols; tm.nums = nums; int q = 0; for(int col = 0; col < cols;col++) { for(int p = 0; p < nums;p++) { if(data[p].column == col) { //从三元组中遍历寻找列号为0的元素开始存放在新的三元组中 tm.data[q].row = data[p].column; tm.data[q].column = data[p].row; tm.data[q].value = data[p].value; q++; } } } return tm; }
测试:
//测试矩阵转置算法 public class TestTranspose { public static void main(String[] args) { int t[][] = {{2,0,5,0,0},{4,0,0,3,0},{0,1,4,0,0}}; SparseMatrix sm = new SparseMatrix(t); sm.printMatrix(); System.out.println("=========矩阵转置后==========="); SparseMatrix tm = sm.transpose(); tm.printMatrix(); } }
-
快速转置
思路:求出原稀疏矩阵的每一列的第一个非0元素在转置后的三元组中的哪个位置,扫描原三元组列上元素依次存放到转置后的三元组位置。稀疏矩阵第一列中的第一个非0元素一定存放在转置后三元组数组的 0 位置,第二列的第一个非0元素的位置等于第一列第一个非0元素在转置后三元组数组的位置加上第一列的非0元素个数,以此类推。
若能先计算出稀疏矩阵中每列非0元素存储在转置后三元组的某个位置,那只需对原三元组扫描一遍即可把元素放在相应的位置上。
例【图1】:
分析可知,可引入两个数组:一个存放每列的非0元素个数num[] 和存放第col列的第一个非0 元素在转置后三元组的位置cpot[]。
即有:
cpot[0] = 0; //第一列的第一个非0 元素一定存放在转置后三元组的第 0 位置。
cpot[col] = cpot[col -1] + num[col -1] (1<=col<=cols -1)
根据上述,对于矩阵A的num和cpot取值如下:
用矩阵A举例:
num[0] =1
cpot[0] = 0 + 0 = 0; //第 0 列的第一个非0元素一定存放在转置后三元组的 0位置
num[1] = 0
cpot[1] = 0 + 1 = 1 //第 0 列 第一个非 0 元素在转置后三元组的位置(0) + 第 0
列的非0元素个数(1)
num[2] = 2
cpot[2] = 1 + 0 = 1 //第 1 列的 第一个非 0 元素在转置后三元组的位置(因为第1列中没有非0
元素,所以再看前一列的第一个非0元素在转置后三元组的位置:1) + 第 1 列的非0元素个数(0)
num[3] = 1
cpot[3] = 1 + 2 = 3 //第 2 列 第一个非 0 元素在转置后三元组的位置(1) + 第 2
列的非0元素个数(2)
num[4] = 1
cpot[4] = 3 + 1 = 4 //第 3 列 第一个非 0 元素在转置后三元组的位置(3) + 第 3
列的非0元素个数(1)
num[5] = 0
cpot[5] = 4 + 1 = 5 //第 4 列 第一个非 0 元素在转置后三元组的位置(4) + 第 4
列的非0元素个数(1)
算法实现:
//快速转置
public SparseMatrix fastranspose() {
SparseMatrix tm = new SparseMatrix(nums);
tm.cols = rows;
tm.rows = cols;
tm.nums = nums;
int j = 0;
int k = 0;
int[] num;
int[] cpot;
if(nums>0) {
num = new int[cols];
cpot = new int[cols];
//每列非0元素个数数组num初始化
for(int i= 0;i < cols; i++) { //①
num[i] = 0;
}
//计算每列非0元素个数
for(int i = 0;i < nums;i++) { //②
/*
* 从头扫描原三元组,利用num数组记录在j列的非0元素个数。
* 若是扫描到不同的列号时,num[j] = num[j] + 1 = 0(因为前面已经把num中所有的 数据都初始化为0) + 1 = 1。
* 若遇到相同的列号则原来的num[j] + 1;
* 直至扫描完整个三元组
*/
j = data[i].column;
num[j]++;
}
cpot[0] = 0;
//计算每一列的第一个非0元素在转置后三元组的位置
for(int i = 1;i < cols;i++) { //③
cpot[i] = cpot[i -1 ] + num[i - 1];
}
//转置操作
for(int i = 0 ;i < nums; i++) { //④
j = data[i].column;
k = cpot[j]; //转置后三元组的位置
tm.data[k].row = data[i].column;
tm.data[k].column = data[i].row;
tm.data[k].value = data[i].value;
/*
* 该列的下一个非0元素的存放位置
* 存放完第一个数据后,应将cpot[j] = cpot[j] + 1 ;否则遇到同列的元素时会把原来的元素覆盖。
*/
cpot[j]++;
}
}
return tm;
}
-
普通转置与快速转置的区别
1、
(1)普通转置需要对原三元组进行循环扫描;
(2)快速转置值需对原三元组进行扫描一遍就可将对应的元素放入相应的位置。2、
(1)普通转置的时间复杂度为:O(n×t),n为稀疏矩阵的列数,t为非0元素的个数;
(2) 快速转置的时间复杂度为:观察上面的算法,可知有4个循环,第一个循环的时间复杂度为 n,第二个为t,第三个为 n-1,第四个为 t。所以 n+t+n-1+t = 2n+2t -1,去掉常数项,得到的时间复杂度为O(n+t),n为稀疏矩阵的列数,t为非0元素个数。