为什么要对矩阵进行压缩存储呢?对于一个n*m的矩阵,我们一般会想到开一个n*m的二维数组来存储,这样计算操作都很方便模拟,但当一个矩阵很大时,这样对于空间的开销与浪费是很可怕的,尤其是当矩阵变成多维时。但我们往往不会在矩阵每一个位置都存有数据,很多矩阵元素其实是0,我们需要记录的只是那些非零元素,于是我们可以记录非零元素的位置与值,这样便可以大大减少空间上的浪费。
矩阵压缩存储代码(注意要按行顺序遍历存储矩阵非零元素):
1 typedef struct { 2 int i, j; //非零元行下标与列下标 3 ElemType e; 4 }Triple; 5 6 typedef struct { 7 Triple data[MAXSIZE + 1]; 8 int mu, nu, tu; //行数, 列数, 非零元个数 9 }TSMatrix;
矩阵转置是矩阵运算中极为重要的操作,但矩阵压缩后我们不能再按原来的方式用n*m的遍历方式进行转置操作,那我们应该怎么进行转置呢,首先想到的是以列为参考值对元素进行遍历转置,这样访问元素的顺序正好是转制后存储的顺序,代码如下:
1 Status TransposeSMatrix(TSMatrix M, TSMatrix & T) { 2 T.mu = M.nu; 3 T.nu = M.mu; 4 T.tu = M.tu; 5 if(T.tu) { 6 int q = 1; 7 for(int col = 1; col <= M.nu; ++col) 8 for(int p = 1; p <= M.tu; ++p) 9 if(M.data[p].j == col) { 10 T.data[q].i = M.data[p].j; 11 T.data[q].j = M.data[p].i; 12 T.data[q].e = M.data[p].e; 13 ++q; 14 } 15 } 16 return OK; 17 }
我们注意到当矩阵不那么稀疏时,比如如果这是一个稠密矩阵甚至n*m的空间里每一个位置都是一个非零元素,这时这个代码的复杂度达到了O(mu*nu*nu),比直接用数组存储的O(mu*nu)还要大一个数量级,这样节省一点空间浪费大量时间的做法显然是不合适的,我们考虑改进算法,我们注意到如果我们预先知道矩阵M每一列的第一个非零元素在T中应有的位置,那么转置中我们就可以直接把他放在那个位置,这时,我们考虑开辅助数组记录每一列第一个非零元素的位置,以及每一列非零元素的数量,但其实,每一列的第一个非零元素的位置就是前一列第一个非零元素位置加上前一列非零元素数量,我们每次维护剩余元素在剩余矩阵中每一列第一个非零元素的位置数组,就得到了下面的O(nu + tu)代码:
1 Status FastTransposeSMtrix(TSMatrix M, TSMatrix &T) { 2 T.mu = M.nu; 3 T.nu = M.mu; 4 T.tu = M.tu; 5 if(T.tu) { 6 int *num = new int[M.nu + 1], *cpot = new int[M.tu + 1]; 7 for(int col = 1; col <= M.nu; ++ col) 8 num[col] = 0; 9 for(int t = 1; t <= M.tu; ++t) ++num[M.data[t].j]; 10 cpot[1] = 1; 11 for(int col = 2; col <= M.nu; ++ col) 12 cpot[col] = cpot[col - 1] + num[col - 1]; 13 for(int p = 1; p <= M.tu; ++p) { 14 int col = M.data[p].j, q = cpot[col]; 15 T.data[q].i = M.data[p].j; 16 T.data[q].j = M.data[p].i; 17 T.data[q].e = M.data[p].e; 18 ++cpot[col]; 19 } 20 delete num; 21 delete cpot; 22 } 23 return OK; 24 }
矩阵转置到此就结束了,写进一个cpp效果就像下面这样,感兴趣的可以自己测试下效果:
1 #include <iostream> 2 #include <cstdio> 3 #include <cstdlib> 4 #include <cmath> 5 #include <algorithm> 6 #include <cstring> 7 #include <vector> 8 #include <string> 9 #include <queue> 10 #include <map> 11 #include <set> 12 13 #define FRER() freopen("in.txt", "r", stdin); 14 #define INF 0x3f3f3f3f 15 16 using namespace std; 17 18 //函数状态码定义 19 #define TRUE 1 20 #define FALSE 0 21 #define OK 1 22 #define ERROR 0 23 #define INFEASIBLE -1 24 //#define OVERFLOW -2 25 26 typedef int Status; 27 typedef int ElemType; 28 29 #define MAXSIZE 12500 30 31 typedef struct { 32 int i, j; //非零元行下标与列下标 33 ElemType e; 34 }Triple; 35 36 typedef struct { 37 Triple data[MAXSIZE + 1]; 38 int mu, nu, tu; //行数, 列数, 非零元个数 39 }TSMatrix; 40 41 Status TransposeSMatrix(TSMatrix M, TSMatrix & T) { 42 T.mu = M.nu; 43 T.nu = M.mu; 44 T.tu = M.tu; 45 if(T.tu) { 46 int q = 1; 47 for(int col = 1; col <= M.nu; ++col) 48 for(int p = 1; p <= M.tu; ++p) 49 if(M.data[p].j == col) { 50 T.data[q].i = M.data[p].j; 51 T.data[q].j = M.data[p].i; 52 T.data[q].e = M.data[p].e; 53 ++q; 54 } 55 } 56 return OK; 57 } 58 59 Status FastTransposeSMtrix(TSMatrix M, TSMatrix &T) { 60 T.mu = M.nu; 61 T.nu = M.mu; 62 T.tu = M.tu; 63 if(T.tu) { 64 int *num = new int[M.nu + 1], *cpot = new int[M.tu + 1]; 65 for(int col = 1; col <= M.nu; ++ col) 66 num[col] = 0; 67 for(int t = 1; t <= M.tu; ++t) ++num[M.data[t].j]; 68 cpot[1] = 1; 69 for(int col = 2; col <= M.nu; ++ col) 70 cpot[col] = cpot[col - 1] + num[col - 1]; 71 for(int p = 1; p <= M.tu; ++p) { 72 int col = M.data[p].j, q = cpot[col]; 73 T.data[q].i = M.data[p].j; 74 T.data[q].j = M.data[p].i; 75 T.data[q].e = M.data[p].e; 76 ++cpot[col]; 77 } 78 delete num; 79 delete cpot; 80 } 81 return OK; 82 } 83 84 int main() 85 { 86 //FRER() 87 88 TSMatrix MatrixA, MatrixB; 89 cin >> MatrixA.mu >> MatrixA.nu >> MatrixA.tu; 90 for(int i = 1; i <= MatrixA.tu; ++i) 91 cin >> MatrixA.data[i].i >> MatrixA.data[i].j >> MatrixA.data[i].e; 92 //TransposeSMatrix(MatrixA, MatrixB); 93 FastTransposeSMtrix(MatrixA, MatrixB); 94 for(int i = 1; i <= MatrixB.tu; ++i) 95 cout << MatrixB.data[i].i << ' ' << MatrixB.data[i].j << ' ' << MatrixB.data[i].e << endl; 96 97 return 0; 98 } 99 100 /*测试数据 101 6 7 8 102 1 2 12 103 1 3 9 104 3 1 -3 105 3 6 14 106 4 3 24 107 5 2 18 108 6 1 15 109 6 4 -7 110 */
接下来是压缩矩阵的乘法,类似转置,我们直接在矩阵结构体里开一个数组用来存矩阵每一行第一个非零元素的在M中位置,就像这样:
1 typedef struct { 2 Triple data[MAXSIZE + 1]; //非零元三元组表 3 int rops[MAXSIZE + 1]; //各行第一个非零元位置 4 int mu, nu, tu; //行数,列数,非零元个数 5 }RLSMatrix;
由于稀疏矩阵相乘不一定还是稀疏矩阵,所以我们要根据结果判断元素是否是非零元,模拟乘法运算如下:
1 Status MultSMatrix(RLSMatrix M, RLSMatrix N, RLSMatrix & Q) { 2 if(M.nu != N.mu) return ERROR; 3 Q.mu = M.mu; 4 Q.nu = N.nu; 5 Q.tu = 0; 6 int *ctemp = new int[Q.nu + 1]; 7 8 if(M.mu * N.nu != 0) { 9 for(int arow = 1; arow <= M.mu; ++arow) { 10 int tp; 11 memset(ctemp, 0, (Q.nu + 1) * sizeof(int)); 12 Q.rops[arow] = Q.tu + 1; 13 if(arow < M.mu) 14 tp = M.rops[arow + 1]; 15 else 16 tp = M.tu + 1; 17 for(int p = M.rops[arow]; p < tp; ++p) { //对当前行的每一个非零元进行计算 18 int brow = M.data[p].j; //找到对应元在N中的标号 19 int t; 20 if(brow < N.mu) t = N.rops[brow + 1]; 21 else t = N.tu + 1; 22 for(int q = N.rops[brow]; q < t; ++q) { 23 int ccol = N.data[q].j; 24 ctemp[ccol] += M.data[p].e * N.data[q].e; 25 } 26 } 27 for(int ccol = 1; ccol <= Q.nu; ++ccol) //压缩存储该行非零元 28 if(ctemp[ccol]) { 29 if(++Q.tu > MAXSIZE) return ERROR; 30 Q.data[Q.tu].i = arow; 31 Q.data[Q.tu].j = ccol; 32 Q.data[Q.tu].e = ctemp[ccol]; 33 } 34 } 35 } 36 return OK; 37 }