稀疏矩阵的定义
对于那些零元素数目远远多于非零元素数目,并且非零元素的分布没有规律的矩阵称为稀疏矩阵(sparse)。
人们无法给出稀疏矩阵的确切定义,一般都只是凭个人的直觉来理解这个概念,即矩阵中非零元素的个数远远小于矩阵元素的总数,并且非零元素没有分布规律。
稀疏矩阵的压缩存储
由于稀疏矩阵中非零元素较少,零元素较多,因此可以采用只存储非零元素的方法来进行压缩存储。
由于非零元素分布没有任何规律,所以在进行压缩存储的时侯需要存储非零元素值的同时还要存储非零元素在矩阵中的位置,即非零元素所在的行号和列号,也就是在存储某个元素比如aij的值的同时,还需要存储该元素所在的行号i和它的列号j,这样就构成了一个三元组(i,j,a[i][j])的线性表。
三元组可以采用顺序表示方法,也可以采用链式表示方法,这样就产生了对稀疏矩阵的不同压缩存储方式。
稀疏矩阵的存储方式
1.稀疏矩阵的三元组表示
若把稀疏矩阵的三元组线性表按顺序存储结构存储,则称为稀疏矩阵的三元组顺序表。
顺序表中除了存储三元组外,还应该存储矩阵行数、列数和总的非零元素数目,这样才能唯一的确定一个矩阵。
typedef struct Triple { int row,col,e; }Triple; typedef struct TSMarix { Triple data[max+1];//data是非零元素三元组表,data[0]未用 int m,n,len;//m:矩阵的行数,n:矩阵的列数,len:非零元素的个数 }TSMarix;
稀疏矩阵的转置
行列递增转置法---算法思想:
采用按照被转置矩阵三元组表A的序列(即转置后三元组表B中的行序)递增的顺序进行转置,并以此送入转置后矩阵的三元组表B中,这样一来,转置矩阵的三元组表B恰好是以“行序为主序”的
这种方法中,转置后的三元组表B仍按行序递增存放,必须多次扫描被转置矩阵的三元组表A,以保证被转置矩阵递增形式进行转置,因此要通过双重循环来完成
#include <stdio.h> #include <stdlib.h> #define max 3 typedef struct Triple { int row,col,e; }Triple; typedef struct TSMarix { Triple data[max+1];//data是非零元素三元组表,data[0]未用 int m,n,len;//m:矩阵的行数,n:矩阵的列数,len:非零元素的个数 }TSMarix; void CreateTSMarix(TSMarix *T) { int i; printf("请输入矩阵的行数,列数,非零元素个数:\n") ; scanf("%d%d%d",&T->m,&T->n,&T->len); if(T->m<1||T->n<1||T->len<1||T->len>=max) { printf("输入的数据有误!\n"); exit(1); } printf("请输入非零元素的行下标,列下标,值(空格分开输入):\n"); for(i=1;i<T->len+1;i++) scanf("%d%d%d",&T->data[i].row,&T->data[i].col,&T->data[i].e); } void TransposeTSMarix(TSMarix T,TSMarix *B)//B保存转置后的矩阵 { B->n=T.m;//转置后B的列存储的T的行 B->m=T.n;//转置后B的行存储的T的列 B->len=T.len;//B的元素的个数和T的元素的个数一样不变 int i,j,k; if(B->len>0) { j=1;//转置后B元素中的下标 for(k=1;k<=T.m;k++)//采用被转置矩阵的列序即转置后矩阵的行序增序排列 { for(i=1;i<=T.len;i++) { if(T.data[i].col==k)//从头到尾扫描表A,寻找col值为k的三元组进行转置 { B->data[j].row=T.data[i].col; B->data[j].col=T.data[i].row; B->data[j].e=T.data[i].e; j++; } } } } } void Print(TSMarix T) { int i; printf("元素的行下标,列下标,值为:\n"); for(i=1;i<=T.len;i++) { printf("\t%d\t%d\t%d\n",T.data[i].row,T.data[i].col,T.data[i].e); } } int main() { TSMarix T,B; printf("创建三元组表:\n"); CreateTSMarix(&T); printf("输出三元组表:\n"); Print(T); TransposeTSMarix(T,&B); printf("转置后的矩阵B为:\n"); Print(B); return 0; }
一次快速定位转置法---算法思想
1.待转置矩阵三元组表A每一列非零元素的总个数,即转置后矩阵三元组表B的每一行中非零元素的总个数
2.待转置矩阵每一列中第一个非零元素在三元组表B中的正确位置,即转置后矩阵每一行中的第一个非零元素在三元组表B中的正确位置,需要增设两个数组,num[col]用来存放三元组表A第col列非零元素总个数(三元组表第col行非零元素总个数),position[col]用来存放转置前三元组表A中第col列(转置后三元组表B中第col行)中第一个非零元素在三元组表B中的存储位置(下标值)。num[col]:将三元组表A扫描一遍,对于其列号为col的元素,给相应的num数组下标为col的元素加一(下标计数法);position[col]:position[1]=1,表示三元组表A中,列值为1的第一个非零元素在三元组表B中的下标值;position[col]=position[col-1]+num[col-1],其中2<=col<=A.n
通过position[col]的初始值为三元组表A中第col列(三元组表B第col行)中第一个非零元素正确的位置,当三元组表A第col列有一个元素加入到三元组表B时,则position[col]=position[col]+1,令position[col]始终指向三元组表A第col列中下一个非零元素在三元组表B中的正确存放位置。
#include <stdio.h> #include <stdlib.h> #define max 10 typedef struct { int row,col; int e; }Triple; typedef struct { Triple data[max+1]; int m,n,len; }TSMatrix; void Create(TSMatrix *T) { printf("请输入矩阵行数,列数,非零元素个数:\n"); scanf("%d%d%d",&T->m,&T->n,&T->len); if(T->m<1||T->n<1||T->len<1||T->len>max) { printf("输入的数据有误!\n"); exit(1); } printf("请输入元素的行下标,列下标,值:\n"); for(int i=1;i<T->len+1;i++) scanf("%d%d%d",&T->data[i].row,&T->data[i].col,&T->data[i].e); } void FastTranspose(TSMatrix T,TSMatrix *B) { int col,t,p,q; int num[max],position[max]; B->len=T.len; B->m=T.n; B->n=T.m; if(B->len>0) { for(col=1;col<=T.n;col++) num[col]=0; for(t=1;t<=T.len;t++)//采用数组下标计数法计算每一列非零元素的个数 num[T.data[t].col]++; position[1]=1; for(col=2;col<=T.n;col++)//求col列中第一个非零元素在B.data[]中的正确位置 position[col]=position[col-1]+num[col-1]; for(p=1;p<=T.len;p++) { col=T.data[p].col; q=position[col]; B->data[q].row=T.data[p].col; B->data[q].col=T.data[p].row; B->data[q].e=T.data[p].e; position[col]++; } } } void Print(TSMatrix T) { printf("\t行下标\t列下标\t值:\n"); for(int i=1;i<T.len+1;i++) printf("\t%d\t%d\t%d\n",T.data[i].row,T.data[i].col,T.data[i].e); } int main() { TSMatrix T,B; printf("创建三元组:\n"); Create(&T); printf("输入的三元组为:\n"); Print(T); printf("快速排序后的三元组为:\n"); FastTranspose(T,&B); Print(B); return 0; }
十字链表
十字链表结点分为三类 :
表结点,它由五个域组成,其中i和j存储的是结点所在的行和列,right和down存储的是指向十字链表中该结点所有行和列的下一个结点的指针,v用于存放元素值。
行头和列头结点,这类结点也有域组成,其中行和列的值均为零,没有实际意义,right和down的域用于在行方向和列方向上指向表结点,next用于指向下一个行或列的表头结点。
总表头结点,这类结点与表头结点的结构和形式一样,只是它的i和j存放的是矩阵的行和列数。
十字链表可以看作是由各个行链表和列链表共同搭建起来的一个综合链表,每个结点aij既是处在第i行链表的一个结点,同时也是处在第j列链表上的一个结点,就你是处在十字交叉路口上的一个结点一样,这就是十字链表的由来。
十字链表中的每一行和每一列链表都是一个循环链表,都有一个表头结点。