一个阶数较大的矩阵中的非零元素个数s相对于矩阵元素的总个数t 十分小时,即s<<t时,称该矩阵为稀疏矩阵。
例如一100×100的矩阵,若其中只有100个非零元素,就可称其为稀疏矩阵。
一、 稀疏矩阵的三元组表示(顺序)
稀疏矩阵的压缩存储方法是只存储非零元素。
由于稀疏矩阵中非零元素的分布没有任何规律,所以在存储非零元素时还必须同时存储该非零元素所对应的行下标和列下标。
稀疏矩阵中的每一个非零元素需由一个三元组: (i, j, a i,j),其中i为行,j为列,a i,j表示非零元素。
唯一确定,稀疏矩阵中的所有非零元素构成三元组线性表。
假设有一个6×7阶稀疏矩阵A(为图示方便,所取的行列数都很小),A中元素如下图所示。则对应的三元组线性表为:
((0,2,1),(1,1,2),(2,0,3),(3,3,5), (4,4,6),(5,5,7),(5,6,4))
若把稀疏矩阵的三元组线性表按顺序存储结构存储,则称为稀疏矩阵的三元组顺序表。
则三元组顺序表的数据结构可定义如下:
#define MaxSize 100 //矩阵中非零元素最多个数
typedef struct
{ int r; //行号
int c; //列号
ElemType d; //元素值
} TupNode; //三元组定义
typedef struct
{ int rows; //行数值
int cols; //列数值
int nums; //非零元素个数
TupNode data[MaxSize];
} TSMatrix; //三元组顺序表定义
其中约定:data域中表示的非零元素通常以行序为主序顺序排列,它是一种下标按行有序的存储结构。
这种有序存储结构可简化大多数矩阵运算算法。
(1)从一个二维矩阵创建其三元组表示
以行序方式扫描二维矩阵A,将其非零的元素插入到三元组t的后面。 void CreatMat(TSMatrix &t,ElemType A[M][N])
{ int i,j; t.rows=M;t.cols=N;t.nums=0;
for (i=0;i<M;i++)
{ for (j=0;j<N;j++)
if (A[i][j]!=0) //只存储非零元素
{ t.data[t.nums].r=i;
t.data[t.nums].c=j;
t.data[t.nums].d=A[i][j];
t.nums++;
}
}
}(2)三元组元素赋值:执行A[i][j]=x 运算
分为两种情况:①将一个非0元素修改为另一个非0值,如A[5][6]=8。②将一个0元素修改为非0值。如A[3][5]=8
bool Value(TSMatrix &t,ElemType x,int i,int j)
{ int k=0,k1;
if (i>=t.rows || j>=t.cols) return false; //失败时返回false
while (k<t.nums && i>t.data[k].r) k++; //查找行
while (k<t.nums && i==t.data[k].r && j>t.data[k].c) k++; //查找列
if (t.data[k].r==i && t.data[k].c==j) //存在这样的元素
t.data[k].d=x;
else //不存在这样的元素时插入一个元素
{ for (k1=t.nums-1;k1>=k;k1--)
{ t.data[k1+1].r=t.data[k1].r;
t.data[k1+1].c=t.data[k1].c;
t.data[k1+1].d=t.data[k1].d;
}
t.data[k].r=i;t.data[k].c=j;t.data[k].d=x;
t.nums++;
}
return true; //成功时返回true
}
(3)将指定位置的元素值赋给变量 执行x=A[i][j]
先在三元组t中找到指定的位置,将该处的元素值赋给x。算法如下:
bool Assign(TSMatrix t,ElemType &x,int i,int j)
{ int k=0;
if (i>=t.rows || j>=t.cols)
return false; //失败时返回false
while (k<t.nums && i>t.data[k].r) k++; //查找行
while (k<t.nums && i==t.data[k].r && j>t.data[k].c) k++;//查找列
if (t.data[k].r==i && t.data[k].c==j)
x=t.data[k].d;
else
x=0; //在三元组中没有找到表示是零元素
return true; //成功时返回true
}
(4)输出三元组
从头到尾扫描三元组t,依次输出元素值。算法如下:
void DispMat(TSMatrix t){ int i;
if (t.nums<=0) return;
printf(“\t%d\t%d\t%d\n",t.rows,t.cols,t.nums);
printf(" ------------------\n");
for (i=0;i<t.nums;i++)
printf("\t%d\t%d\t%d\n",
t.data[i].r,t.data[i].c, t.data[i].d);
}
(5)矩阵转置
对于一个m×n的矩阵Am×n,其转置矩阵是一个n×m的矩阵Bn×m,满足bi,j=aj,i,其中0≤i≤m-1,0≤j≤n-1。
void TranTat(TSMatrix t,TSMatrix &tb)
{ int p,q=0,v; //q为tb.data的下标
tb.rows=t.cols;tb.cols=t.rows;tb.nums=t.nums;
if (t.nums!=0) //当存在非零元素时执行转置
{ for (v=0;v<t.cols;v++) //tb.data[q]中的记录以c域的次序排列
for (p=0;p<t.nums;p++) //p为t.data的下标
if (t.data[p].c==v)
{ tb.data[q].r=t.data[p].c;
tb.data[q].c=t.data[p].r;
tb.data[q].d=t.data[p].d;
q++;
}
}
}
以上算法的时间复杂度为O(t.cols*t.nums),而将二维数组存储在一个m行n列矩阵中时,其转置算法的时间复杂度为O(m*n)。最坏情况是当稀疏矩阵中的非零元素个数t.nums和m*n同数量级时,上述转置算法的时间复杂度就为O(m*n2)。
二、稀疏矩阵的十字链表表示
十字链表为稀疏矩阵的每一行设置一个单独链表,同时也为每一列设置一个单独链表。
这样稀疏矩阵的每一个非零元素就同时包含在两个链表中,即每一个非零元素同时包含在所在行的行链表中和所在列的列链表中。这就大大降低了链表的长度,方便了算法中行方向和列方向的搜索,因而大大降低了算法的时间复杂度。
对于一个m×n的稀疏矩阵,每个非零元素用一个节点表示,节点结构可以设计成如下图(a)所示结构。
其中i、j、value分别代表非零元素所在的行号、列号和相应的元素值;down和right分别称为向下指针和向右指针,分别用来链接同列中和同行中的下一个非零元素节点。
十字链表中设置行头节点、列头节点和链表头节点。它们采用和非零元素节点类似的节点结构,具体如上图(b)所示。
其中行头节点和列头节点的i、j域值均为0;行头节点的right指针指向该行链表的第一个节点,它的down指针为空;
列头节点的down指针指向该列链表的第一个节点,它的right指针为空。
行头节点和列头节点必须顺序链接,这样当需要逐行(列)搜索时,才能一行(列)搜索完后顺序搜索下一行(列),行头节点和列头节点均用link指针完成顺序链接。
十字链表节点结构和头节点的数据结构可定义如下:
#define M 3 //矩阵行#define N 4 //矩阵列
#define Max ((M)>(N)?(M):(N)) //矩阵行列较大者
typedef struct mtxn
{ int row; //行号
int col; //列号
struct mtxn *right,*down; //向右和向下的指针
union
{ int value;
struct mtxn *link;
} tag;
} MatNode; //十字链表类型声明