5.3.2 稀疏矩阵的压缩存储方法
什么是稀疏矩阵?人们无法给出确切的定义,它只是一个凭人们的直觉来了解的概念。假设在m×n的矩阵中,有t个非零元素,若t远远小于矩阵元素的总数(即t<<m×n),则称A为稀疏矩阵。
精确点,设在矩阵A中,有t个非零元素。令,称δ为矩阵的稀疏因子。通常认为δ≤0.05时称之为稀疏矩阵。
抽象数据类型稀疏矩阵的定义如下:
ADT SpareseMatrix{
数据对象:D={aij|i=1,2,..,m,j=1,2,..,n;aij∈ElemSet,
m和n分别称为矩阵的行数和列数 }
数据关系:R={Row,Col}
Row={<ai,j,ai,j+1>|1≤i≤m,1≤i≤n-1}
Row={<ai,j,ai+1,j>|1≤i≤m-1,1≤i≤n}
基本操作:
CreateSMatrix(&M);
操作结果:创建稀疏矩阵M。
DestroySMatrix(&M);
初始条件:稀疏矩阵M存在。
操作结果:销毁稀疏矩阵M。
PrintSMatrix(M);
初始条件:稀疏矩阵M存在。
操作结果:输出稀疏矩阵M。
CopySMatrix(M,&T);
初始条件:稀疏矩阵M存在。
操作结果:由稀疏矩阵M复制得到T。
AddSMatrix(M,N,&Q);
初始条件:稀疏矩阵M与N的行数和列数对应相等。
操作结果:求稀疏矩阵的和Q=M+N。
SubSMatrix(M,N,&Q);
初始条件:稀疏矩阵M与N的行数和列数对应相等。
操作结果:求稀疏矩阵的差Q=M-N。
MultSMatrix(M,N,&Q);
初始条件:稀疏矩阵M的列数等于N的行数。
操作结果:求稀疏矩阵乘积Q=M×N。
TransposeSMatrix(M,&T);
初始条件:稀疏矩阵M存在。
操作结果:求稀疏矩阵M的转置矩阵T。
} ADT SpareseMatrix
如何进行稀疏矩阵的压缩存储呢?
若以常规方法,即以二维数组表示高阶的稀疏矩阵,则:
◆ 零值元素占的空间很大;
◆ 计算中进行了很多和零值的运算;
解决问题的原则:
◆ 尽可能少存或不存零元素;
◆ 尽可能减少没有实际意义的运算;
◆ 运算方便; 即:
能尽可能快地找到与下标值(i,j)对应的非零值元;
能尽可能快地找到同一行或同一列的非零值元;
在存储稀疏矩阵时,为了节省存储单元,很自然地想到使用压缩存储方法。但由于非零元素的分布一般是没有规律的,因此在存储非零元素的同时,还必须同时记下它所在的行和列的位置(i,j)。反之,一个三元组(i,j,aij)唯一确定了矩阵A的一个非零元。因此,稀疏矩阵可由表示非零元的三元组及其行列数唯一确定。例如,下列三元组表
((1,2,12)(1,3,9),(3,1,- 3),(3,6,14),(4,3,24), (5,2,18),(6,1,15),(6,4,-7))
加上(6,7)这一对行、列值便可作为下图中矩阵M的另一种描述。而由上述三元组表的不同表示方法可引出稀疏矩阵不同的压缩存储方法。
M= T=
稀疏矩阵M和T
下面讨论(随机)稀疏矩阵的几种压缩存储处理方法。
一、三元组顺序表
若将表示稀疏矩阵的非零元素的三元组按行优先(或列优先)的顺序排列,则得到一个其结点均是三元组的线性表,我们将该线性表的顺序储结构称为三元组顺序表。
//--------------稀疏矩阵的三元组顺序表存储表示--------------------------
#define MAXSIZE 12500 // 假设非零元个数的最大值为12500
typedef struct {
int i, j; // 该非零元的行下标和列下标
ElemType e;
} Triple; // 三元组类型
typedef struct {
Triple data[MAXSIZE + 1]; // 非零元三元组表,data[0]未用
int mu, nu, tu; // 矩阵的行数、列数和非零元个数
} TSMatrix; // 稀疏矩阵类型
A4×5= B5×4=
i j v |
i j v | ||||||||||||||||||||||||||||||||||||||||||||||||
|
| ||||||||||||||||||||||||||||||||||||||||||||||||
稀疏矩阵A和它的三元组表a.data |
稀疏矩阵B和它的三元组表b.data |
下面以矩阵的转置为例,说明在这种压缩存储结构上如何实现矩阵的运算。
一个m×n的矩阵A,它的转置B是一个n×m的矩阵,且a[i][j]=b[j][i],1≤i≤m,1≤j≤n,即A的行是B的列,A的列是B的行。
将A转置为B,就是将A的三元组表a.data置换为表B的三元组表b.data,如果只是简单地交换a.data中i和j的内容,那么得到的b.data将是一个按列优先顺序存储的稀疏矩阵B,要得到按行优先顺序存储的b.data,就必须重新排列三元组的顺序。
由于A的列是B的行,因此,按a.data的列序转置,所得到的转置矩阵B的三元组表b.data必定是按行优先存放的。按这种方法设计的算法,其基本思想是:对A中的每一列col(1≤col≤n),通过从头至尾扫描三元表a.data,找出所有列号等于col的那些三元组,将它们的行号和列号互换后依次放入b.data中,即可得到B的按行优先的压缩存储表示。
算法5.1
Status TransposeSMatrix(TSMatrix M, TSMatrix &T) { // 采用三元组顺序表存储表示,求稀疏矩阵M的转置矩阵T T.mu=M.nu; T.nu=M.mu; T.tu=M.tu; if (T.tu) { q=1; for (col=1; col<=M.nu; ++col) for (p=1; p<=M.tu; ++p) { // 转置矩阵元素 if(M.data[p].j==col){ T.data[q].i=M.data[p].j; T.data[q].j=M.data[p].i; T.data[q].e=M.data[p].e; q++;} } // for } // if return OK; } // TransposeSMatrix |
分析这个算法,主要的工作是在p和col的两个循环中完成的,故算法的时间复杂度为O(nu×tu),即矩阵的列数和非零元的个数的乘积成正比。而一般传统矩阵的转置算法为:
for(col=1;col<=nu;++col)
for(row=1;row<=mu;++row)
t[col][row]=m[row][col];
其时间复杂度为O(nu×mu)。它正比于行数和列数的乘积。由于非零元素个数一般远远大于行数,因此上述稀疏矩阵转置矩阵算法的时间大于通常的转置算法的时间。三元组顺序表虽然节省了存储空间,但时间复杂度比一般矩阵转置的算法还要复杂,同时还有可能增加算法的难度。因此,此算法仅适用于tu<<mu×nu的情况。