稀疏矩阵
稀疏概念:当矩阵中只有很少的非零元素时,并且分布也不规律,非零元素只占20%~30%甚至更少,此矩阵称为稀疏矩阵
如果按照传统的存储方式例如数组存储会浪费大量空间(用于存储非零元素
或者相同常数
),但如果只存储少量的非零元素,而不存储大量的零或者相同常数,从而达到压缩存储的目的。但很多非零元素位置分布没有规律,所以需要添加一些辅助信息,例如行号,列号用于存储位置。稀疏矩阵的压缩存储结构分为两类:三元组顺序表,十字链表。
三元组顺序表
(1)三元组顺序表表示
typedef struct
{
int data;
int row,col;
}triple; //数组元素信息,行号,列号,数据信息
typedef struct
{
triple data[max]; //数组元素
int rows,cols,nums; //行数,列数,数组元素总数
}trix;
(2)转置运算
1.按列递增转置
#include<stdio.h>
#include<stdlib.h>
#define max 20
typedef struct
{
int data;
int row,col;
}triple; //数组元素信息,行号,列号,数据信息
typedef struct
{
triple data[max]; //数组元素
int rows,cols,nums; //行数,列数,数组元素总数
}trix;
void transform(trix *A,trix *B)
{
int i,j,k;
B->cols=A->rows; B->rows=A->cols;//转置后行变为列,列变为行
B->nums=A->nums;
if(B->nums>0)
{
k=1;
for(i=1;i<=A->cols;i++) //从A的第一列开时找B就会按先行序后列序的方式存储
for(j=1;j<=A->nums;j++) //从第一个元素开始找到满足的元素
if(A->data[j].col==i) //行列,数据交换
{
B->data[k].col=A->data[j].row;
B->data[k].row=A->data[j].col;
B->data[k].data=A->data[j].data;
k++;
}
} //该算法的时间主要浪费在双从循环中
}
int main(void)
{
int i,rows,cols,num;
trix *A,*B;
A=(trix*)malloc(sizeof(trix));
B=(trix*)malloc(sizeof(trix));
scanf("%d%d%d",&A->rows,&A->cols,&A->nums);
for(i=1;i<=A->nums;i++)
scanf("%d %d %d",&A->data[i].row,&A->data[i].col,&A->data[i].data);
transform(A,B);
for(i=1;i<=B->nums;i++)
printf("%d %d %d\n",B->data[i].row,B->data[i].col,B->data[i].data);
}
改进:
0
|
14
|
0
|
0
|
0
|
0
|
0
|
0
|
-7
|
0
|
0
|
0
|
0
|
0
|
36
|
0
|
0
|
0
|
0
|
0
|
0
|
如果该矩阵又从第一列找到最后一列则会浪费时间
row col data row col data
1 |
2
|
14
|
|
1
|
3
|
36
|
2
|
2
|
-7
|
转制后
|
2
|
1
|
14
|
3
|
1
|
36
|
|
2
|
2
|
-7
|
void transform(trix *A,trix *B)
{
int i,j,k;
B->cols=A->rows; B->rows=A->cols;//转置后行变为列,列变为行
B->nums=A->nums;
if(B->nums>0)
{
k=1;
for(i=1;i<=A->cols;i++) //从A的第一列开时找B就会按先行序后列序的方式存储
for(j=1;j<=A->nums;j++) //从第一个元素开始找到满足的元素
if(A->data[j].col==i) //行列,数据交换
{
B->data[k].col=A->data[j].row;
B->data[k].row=A->data[j].col;
B->data[k].data=A->data[j].data;
k++;
}
if(K>B->nums) break;//终止循环避免不必要的循环
}
}
改进2:
2
|
0
|
0
|
0
|
0
|
0
|
0
|
0
|
0
|
0
|
1
|
0
|
0
|
0
|
0
|
0
|
3
|
0
|
0
|
0
|
0
|
0
|
0
|
0
|
0
|
0
|
0
|
0
|
0
|
0
|
0
|
0
|
0
|
稀疏矩阵之间间隔很大所以控制外层循环,找到列的最小值
row col data row col data
1
|
1
|
2
|
|
1
|
1
|
2
|
2
|
6
|
3
|
转制后
|
6
|
2
|
3
|
1
|
11
|
1
|
|
11
|
1
|
1
|
void transform(trix *A,trix *B)
{
int i=1,j,k;
B->cols=A->rows;B->nums=A->nums;
B->rows=A->cols;
while(i<=A->nums)
{
min=1;
for(j=2;j<A->nums;j++)
if(A->data[j].col<A->data[min].col)//找到当前三元组中列最小的元素
min=j;
B->data[i].row=A->data[min].col;
B->data[i].col=A->data[min].row;
B->data[i].data=A->data[min].data;
i++;
A->data[j].col=A->cols+1;//将当前最小元素变为最大列值,即下次不比较
}
}
2.一次定位快速转置
此算法对被转置的三元矩阵只扫描一次,在转置前先求得每一列非零元素的个数进而求得每一列的第一个元素的位置
void transform(trix *A,trix *B)
{
int i,j,k,co;
int num[max],postion[max];
B->cols=A->rows; B->rows=A->cols;
B->nums=A->nums;
if(B->nums>0)
{
for(i=1;i<=A->cols;i++)
num[i]=0;
for(i=1;i<=A->cols;i++)//扫描A数组统计出每一列的非零元素个数将其放到num数组中(num[A->data[i].col存放第A->data[i].col列的非零元素个数)
num[A->data[i].col]++;
postion[1]=1; //原数组中第一列的位置从一开始
for(i=2;i<=A->cols;i++)//计算原矩阵每一列的第一个元素在列中的位置
postion[i]=postion[i-1]+num[i-1];
for(j=1;j<=A->nums;j++)
{
co=A->data[j].col; //co为元素的列
k=postion[co]; //根据元素的列找到它的位置
B->data[k].col=A->data[j].row;
B->data[k].row=A->data[j].col;
B->data[k].data=A->data[j].data;
postion[co]++;
}
}
}
//以此表为例,例如第一列第一个元素1位置就为一,而第二个元素2在后边postion[co]++
,因此同列会+1的增长,为2,而第二列后边则为postion[i]=postion[i-1]+num[co-1],
所以第二列变为1+2=3
,即第二列元素位置变为3,但第二列没有非零元素,所以第四列第一个元素位置变为3+0=3,所以
postion数组函数
记录
从第一列到最后一列每一个非零元素的位置
例如此表中未转置前元素按行优先为1,3,5,2,
4,6(位置为123456)但转置后为1,2,3,4,5,6(位置为123456).
十字链表
用三元组表示稀疏矩阵时,对于矩阵的动态操作比较困难,因此用链式存储的十字链表更为恰当,能够灵活的使用插入和删除操作。
下表为十字链表的基本意义
十字链表的创建
typedef struct OLnode
{
int row,col,data;
struct OLnode *right,*down; //除了基本的行列和数据以外还加入两个方向指针(right,down),用于指向行和列,下一个存储非零元素的节点
}OLnode; //right链接同一行中的下一个元素,down用于链接同一列的下一个元素
typedef struct
{
OLnode *rowhead,*colhead; //用于两个以为指针数组存储每一行,每一列的第一个节点的指针
int rows,cols,nums;
}Crosslist;
Crosslist *init_Crosslist(int **A,int m,int n)
{
int i,j;
OLnode *p,*q;
Crosslist *cl;
cl=(Crosslist*)malloc(sizeof(Crosslist));
cl->rows=m;cl->cols=n;cl->nums=0;
cl->rowhead=(OLnode*)malloc(m*sizeof(OLnode)); //分配两个一维数组存储行列第一个节点并置为空节点
for(i=0;i<m;i++)
cl->rowhead[i].right=NULL;
cl->colhead=(OLnode*)malloc(n*sizeof(OLnode));
for(i=0;i<n;i++)
cl->colhead[i].down=NULL;
for(i=0;i<m;i++)
for(j=0;j<n;j++)
{
if(A[i][j]!=0) //非零元素即存入十字链表
{
p=(OLnode*)malloc(sizeof(OLnode));
p->col=j+1;p->row=i+1;
p->data=A[i][j];
p->down=NULL;p->right=NULL;
if(cl->rowhead[i].right!=NULL)//判断是否为第i行是否为空
{
q=cl->rowhead[i].right;
while(q->right!=NULL&&q->col<j+1)
q=q->right;
p->right=q->right; //把p节点置到该行最后一个
q->right=p;
}
else
cl->rowhead[i].right=p; //若非空则P成为该行第一个节点
if(cl->colhead[j].down!=NULL)
{
q=cl->colhead[j].down;
while(q->down!=NULL&&q->row<i+1)
q=q->down;
p->down=q->down; //把p节点置到该列最后一个
q->down=p->down;
}
else cl->colhead[j].down=p;
}
}
return cl;
}