稀疏矩阵
稀疏矩阵是指矩阵中大多数元素为0的矩阵。从直观上讲,当非零元素个数低于总元素30%时,这样的矩阵称为稀疏矩阵。
稀疏矩阵的三元组表表示法
- 稀疏矩阵的三元组表表示存储
对于稀疏矩阵的亚索存储,采取值存储非零元素的方法,由于非零元素的分布没有规律,因此必须同时存储非零元素的下标,即所处的行号和列号,这就是稀疏矩阵的三元表示法。(以下标从1开始计数)
可以表示为
说明:为方便处理将稀疏矩阵的非零元素对应的三元组按照行序为主列的一维数组结构进行存放,讲句真的每一行从小到大的全部的非零元素按照三元组方式进行按列号递增存放,得到上述矩阵。
- 稀疏矩阵的三元组表的定义
#define MAXSIZE 100
typedef struct
{
int row,col;//定义非零元素的行下标和列下标
ElementType element;//定义非零元素的值
}node;
typedef struct
{
int m,n,length;//定义矩阵的行数列数和非零元素个数
node data[MAXSIZE]
}TSMatrix;
- 三元组表实现稀疏矩阵的转置矩阵
需要强调的是,矩阵常规存储是二维的,而三元组是一维存储,因此运算方法也会有不同。
采用矩阵的常规存储方法实现矩阵转置
void TransMatrix(ElementType source[m][n],ElementType dst[n][m])
{
int i,j;
for(i=0;i<m;i++)
{
for(j=0;j<n;j++)
{
dst[j][i]=source[i][j];
}
}
}
用三元组表实现稀疏矩阵的转置
假设A和B是稀疏矩阵source和dst的三元组表,实现转置的简单方法如下。
1:矩阵source的三元组表A的行,列互换就可以得到B中的元素。
2:转置之后的三元表组并未按照行序为主序存储的,为保证转置后的矩阵三元表组B同样按照行序为主序进行存放,
则需要对行,列互换后的三元表组B按B的行下标(即A的列下标)以递增顺序重新排序
按照上述方法虽然第一步容易实现但是步骤二重新排序势必会移动大量的元素,从而影响算法的效率。
为了避免上述问题,可以有以下两种方法。
方法一:列序递增转置法
算法思想:按照被转置矩阵的三元表组A的列序(即转置后三元组表B的行序)递增的顺序进行转置,并依次送入转置
后的矩阵的三元组表B中,这样一来,转置后矩阵的三元组表B恰好是按照行序为主序的
具体流程:
第一遍从头到尾扫描三元组表A,找出其中所有列(col)为1的三元组,转置之后送入三元组表B中
然后找出所有列(col)为2的三元组,转置之后送入三元组表B中
以此类推找出所有列(col)为k的三元组,转置之后送入三元组表B中(下标从0开始)
算法描述:
void TransposeTSMatrix(TSMatrix A,TSMatrix* B)
{
int i,j,k;
B->m=A.n; B->n=A.m; B->length=A.length;
if(b->length)
{
j=0;
for(k=0;k<n;k++)
{
for(i=0;i<A.length;i++)//扫描三元组表A共n次,n是三元组表原矩阵的列数
{
if(A.data[i].col==k)
{
//分别将A的列数k=0,1,2,...n-1的元素赋给B
B.data[j].col==A.data[i].row;
B.data[j].row==A.data[i].col;
B.data[j].element==A.data[i].element;
j++;
}
}
}
}
}
算法分析:
算法的耗时主要集中在双重循环中,其时间复杂度为O(A.n*A.length)。
方法二:一次定位快速转置法
算法思想:方法一中需要通过啊双重循环来实现行序递增形式的转置为了将被转置矩阵A一次性的定位到B的正确位
置上,需要计算下面数据
1:待转置矩阵每一列非零元素个数,即转置之后B中每一行非零元素个数。
2:待转置矩阵每一列第一个非零元素在三原表B中的正确位置,即转置之后B中每一行第一个非零元素的个数
为此需要设置两个数组,分别为num[],position[],其中num[]存放待转置矩阵每一列非零元素个数
,position[]存放待转置矩阵每一列第一个非零元素在三元组表B中的正确位置
num[]的计算方法为,将三原表组扫描一遍,对应其中的列号,给相应的num下标加1
position[]的计算方法如下:
position[0]=0,表示三元组表A中,列值为0的第一个非零元素在三元组表B中的下标值为0
position[col] = position[col-1]+number[col-1]
上表的对应的num[],position[]值如下表:
一次快速定位转置的具体做法是,position[col]的初值是三元组表A中每一列第一个非零元素在B中的正确位置,当三元组
表A中第col列有一个元素加入到B时,position[col]=position[col]+1,即令position[col]始终指向三元组
表A中第col中下一个非零元素在三元组表B中的正确存放位置
算法描述:
FastTransposeTSMatrix(TSMatrix A,TSMatrix *B)
{
int col,t,p,q;
int num[MAXSIZE],position[MAXSIZE];
B->length=A.length; B->n=A.m; B->m=A.n;
if(B->length)
{
for(col=0;col<A.n;col++)//对num[]进行初始化
num[col]=0;
for(t=0;t<A.length;t++)//对num进行赋值,每一列非零元素个数
num[A.data[t].col]++;
position[0]=0;//三元组表A中,列值为1的第一个非零元素在三元组表B中的下标值
for(col=1;col<A.n;col++)//得到所有的A中每一列第一个非零元素在B中的位置
position[col]=position[col-1]+num[col-1];
for(p=0;p<A.length;p++)
{
col=A.data[p].col; //A中每一个非零元素的列标
q=position[col];//q等于对应非零元素的列标对应B中这一列第一非零个元素的位置
B.data[q].element=A.data[p].element;
B.data[q].col = A.data[p].row;
B.data[q].row = A.data[p].col;
position[col]++;//对第一个非零元素在B中的位置进行更新,使得下一个该列元素进来时在B中不会重复
}
}
}
稀疏矩阵的链式存储结构:十字链表表示法
在十字链表中,矩阵的每一个非零元素用一个节点表示,该节点除了(row,col,element)以外,还要添加以下两个链域:right用于链接同一行中的下一个非零元素,down用于链接同一列中的下一个非零元素。
在十字链表中,同一行的非零元素通过right域连接成一个单链表,同一个列的非零元素通过down域连接成一个单链表。这样任意一个非零元素所对应的节点同时处于一个行和列的节点。同时再附带一个存放所有行链表和列联表的头指针的一维数组。
十字链表的类型定义如下
typedef struct OLNode
{
int row,col; /*非零元素的行下标和列下标 */
ElementType value;
struct OLNode *right,*down; /*非零元素所在航标,列表的后继链域*/
}OLNode; *OLink;
typedef struct
{
OLink * row_head,*col_head;/*行列联表的头指针向量*/
int m,n,length; /*稀疏矩阵的行数列数非零元素个数*/
}