- 矩阵通常采用二维数组形式来表示(逻辑结构)
- 特殊矩阵压缩到一维数组来节省存储空间
- 压缩原则:只存非零元素
- 规律分布矩阵:
① 按规律公式压缩:A[i][j]→B[k],二维下标[i][j]→一维下标[k],映射,地址计算问题;
② 稀疏矩阵:只存非零元素。
三角矩阵:
三角矩阵大体分为:下三角矩阵、上三角矩阵和对称矩阵。
一个n阶矩阵A:
若当i < j时,有aij=0,则称此矩阵为下三角矩阵;
若当i > j时,有aij=0,则此矩阵称为上三角矩阵;
若矩阵中的所有元素均满足aij = aji,则称此矩阵为对称矩阵。
下三角矩阵:
A的行主序序列:a11,a21,a22,a31,a32,a33…an1,an2…ann
。
元素个数为n(n+1)/2
,可压缩存储到一个大小为n(n+1)/2
的一维数组B中。
aij在一维数组中的位置为:
Loc[i][j] = Loc[1][1]+i*(i-1)/2+j-1 (i>=j)
。
Loc[i][j] = 0 (i<j)
。
上三角矩阵:
上三角也可压缩存储到n(n+1)/2个元素的一维数组C中。元素aij(i<j)在数组C中的存储位置为:
Loc[i][j]=Loc[1][1]+j*(j-1)/2+i-1
。
对角矩阵:
aij=aji,可以为每一对相等的元素分配一个存储空间,即只存下三角(或上三角)矩阵,从而将n2个元素压缩到n(n+1)/2
个空间中。
带状矩阵:
矩阵中的所有非零元素都集中在以主对角线为中心的带状区域中。其中最常见的就是三对角带状矩阵。
行主序压缩存储方法:
- 确定存储该矩阵所需的一维向量空间大小:
第一行和最后一行只有两个元素,其余各行均有3个非零元素。由此所需的一维向量空间大小为:3n-2
。 - 确定非零元素aij在一维数组空间中的位置:
Loc[i][j]=Loc[1][1]+(3(i-1)-1)*size+(j-i+1)*size=Loc[1][1]+2(i-1)+j-1
.
稀疏矩阵:
大多数元素为零的矩阵。
一般地,当非零元素个数只占矩阵元素总数的25%—30%及以下。
稀疏矩阵三元组表表示法:
稀疏矩阵的压缩存储:
- 存储非零元素值
- 存储该非零元素在矩阵中所处的行号和列号
三元组表的类型定义:
#define MAXSIZE 1000 /*非零元素的个数最多为1000*/
typedef struct{
int row, col; /*该非零元素的行下标和列下标*/
ElementType e; /*该非零元素的值*/
}Triple;
typedef struct{
Triple data[MAXSIZE+1];//非零元素三元组表,data[0]未用
int m, n, len; /*矩阵的行数、列数和非零元素的个数*/
}TSMatrix;
矩阵转置经典运算:
把位于(row,col)位置上的元素换到(col ,row)位置上。
Void TransMatrix(ElementType source[n][m], ElementType dest[m][n])
{/*Source、dest被转置矩阵和转置后的矩阵(用二维数组表示)*/
int i, j;
for(i = 0; i < m; i++)
for (j = 0; j < n; j++)
dest[i][ j] =source[j] [i] ;
}
三元组表稀疏矩阵的转置运算:
换位:行列互换
矩阵source三元组表A的行、列互换得到B的元素;
重排:行主序排序
转置后的三元组表矩阵B也应以“行主序”存放,需对行、列互换后的三元组B,按行下标(A列下标)重新排序。
算法一:列序递增转置法
【算法思想】:
按被转置矩阵三元组表A的列序递增顺序进行转置。
从头到尾扫描表A第一列、第二列、…元素,依次送入表。
具体做法:
找出第k行全部元素:第k遍扫描三元组表A,找出其中所有col为k的三元组,转置后按顺序送到三元组表B中。
K=1,2,...,A.n (A的列数)
.
算法实现:
void TransposeTSMatrix(TSMatrix A, TSMatrix *B) /*矩阵A三元组表转置到B所指向矩阵*/
{
int i,j,k;
B->m=A.n; B->n=A.m; B->len=A.len;
if(B->len > 0)
{
j = 1;//表B行号
for(k = 1; k <= A.n; k++) //按A列下标检索、排序
for(i = 1; i <= A.len; i++)//搜索整个A中列下标为k的元素存入B中
if(A.data[i].col == k)
{
B->data[j].row = A.data[i].col;
B->data[j].col = A.data[i].row;
B->data[j].e = A.data[i].e;
j++;
}
}
}
空间复杂度降低,时间复杂度不一定改善,T(n) = O(A.n * A.len)
。
所以此方法仅适用于存储稀疏矩阵。
算法二:一次性定位快速转置法
【算法思想】:
只用一重循环完成转置。对A中所有非零元“一次定位”直接放在B中的准确位置。
num[col]
存放A三元组第col列非零元素个数。
position[col]
存放A三元组第col列中第一个非零元素的位置。
position[col]=position[col-1]+num[col-1]
。
FastTransposeTSMatrix(TSMatrix A, TSMatrix *B)
{
int col, t, p,q;
int num[MAXSIZE] = {0}, position[MAXSIZE];
B->len = A.len; B->n = A.m; B->m = A.n ;
if(B->len)
{
for(col = 1; col <= A.n; col++)
num[col] = 0;
for(t = 1; t <= A.len; t++)
num[A.data[t].col]++; /*累计每列元素个数*/
position[1] = 1;
for(col = 2; col <A.n; col++) /*每列首元素在B表的位置*/
position[col] = position[col-1] + num[col - 1];
for(p = 1; p < A.len; p++)
{ col = A.data[p].col; q = position[col]; //取列号,查位置(B下标)
B->data[q].row = A.data[p].col;
B->data[q].col = A.data[p].row; //行列互换
B->data[q].e = A.data[p].e //元素赋值
position[col]++;
}
}
}//更新该列元素插入位置
三元组表实现稀疏矩阵的乘法运算
矩阵M是m1×n1矩阵,矩阵N是m2×n2矩阵,当n1等于m2是,可以相乘。
经典算法:
for(i = 1; i <= m1; i++)
for(j = 1; j <= n2; j++)
{
Q[i][j] = 0;
for(k = 1; k <= n1; k++)
Q[i][j] += M[i][k] * N[k][j];
}
三元组表矩阵乘法:
【算法思想】:
固定三元组M中元素(i,k,Mik)
,在三元组N中找所有行号为k的的对应元素(k,j,kj)
进行相乘、累加得Q[i][j]
。
for(k = 1; k <= n1; k++)
Q[i][j] += M[i][k] * N[k][j]
#define MAXSIZE 1000 /*非零元素的个数最多为1000*/
#define MAXROW 1000 /*矩阵最大行数为1000*/
typedef struct
{
int row, col; /*该非零元素的行下标和列下标*/
ElementType e; /*该非零元素的值*/
}Triple;
typedef struct
{
Triple data[MAXSIZE+1]; /* 非零元素的三元组表,data[0]未用*/
int first[MAXROW+1]; /*三元组表中各行第一个非零元素所在的位置。*/
int m, n, len; /*矩阵的行数、列数和非零元素的个数*/
}TriSparMatrix;
int MulSMatrix(TriSparMatrix M, TriSparMatrix N, TriSparMatrix *Q)/*采用改进的三元组表表示法,求矩阵乘积Q=M×N*/
int arow, brow, p;
int ctemp[MAXSIZE];
if(M.n != N.m) return FALSE; /*返回FALSE表示求矩阵乘积失败*/
Q->m = M.m; Q->n = N.n; Q->len = 0;//初始化
if(M.len * N.len != 0)//M、N都非空
{
for(arrow = 1; arrow <= M.m; arow++) /*逐行处理M*/
{
for(p = 1; p <= M.n; p++)
ctemp[p] = 0 ;/* 当前行各元素累加器清零*/
Q->first[arow] = Q->len + 1;
for(p = M.first[arow]; p<M.first[arrow + 1]; p++) /*p指向M当前行中每个非零元素*/
{
brow=M.data[p].col; /* M中的列号应与N中的行号相等*/
if(brow<N.n)
t=N.first[brow+1];
else
t=N.len+1;
for(q=N.first[brow];q<t;q++)
{
ccol=N.data[q].col; /*乘积元素在Q中列号*/
ctemp[ccol]+=M.data[p].e*N.data[q].e; } /* for q */
} /*求得Q中第crow行的非零元*/
for(ccol=1;ccol<Q->n;col++) /*压缩存储该非零元*/
if(ctemp[ccol])
{
if(++Q->len>MAXSIZE) return 0;
Q->data[Q->len]={arow, ccol, ctemp[ccol]};
}/* if */
}/* for arow */
}/*if*/
return(TRUE); /*返回TRUE表示求矩阵乘积成功*/
}
时间复杂度为O(A.n+A.len)
.
稀疏矩阵链式存储结构:十字链表
链式存储(十字链表)结构:
能够灵活地插入因运算而产生的新非零元素,删除新产生的零元素,实现矩阵的各种运算。
十字链表矩阵,每个非零元素用一个结点表示.
- 除了(row,col,value)以外,还要有两个域:
- 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, len; /*矩阵行数、列数、非零元素个数*/
}CrossList;
十字链表建立算法:
CreateCrossList (CrossList * M)
{
if(M! = NULL) free(M);
scanf(&m, &n, &t); /*输入行、列和非零元素的个数*/
M->m = m; M->n = n; M->len = t;
if(!(M->row_head = (Olink*)malloc((m+1)sizeof(OLink)))) exit(OVERFLOW);
if(!(M->col_head = (OLink*)malloc((n+1)sizeof(OLink)))) exit(OVERFLOW);
M->row_head[ ] = M->col_head[ ]=NULL; /*初始化行、列头指针向量(数组)*/
for(scanf(&i, &j, &e); i != 0; scanf(&i,&j,&e))
{
if(!(p = (OLNode *) malloc(sizeof(OLNode)))) exit(OVERFLOW);
p->row = i; p->col = j; p->value = e; /*生成结点*/
if(M->row_head[i] == NULL) M->row_head[i] = p;//是该行第1个结点
else
{ /*寻找行表中的插入位置,列号排序*/
for(q = M->row_head[i]; q->right && q->right->col< j; q = q->right)
p->right = q->right; q->right = p; /*q跟踪插入位置前结点*/
}
if(M->col_head[j] == NULL) M->col_head[j] = p;
else
{ /*寻找列表中的插入位置*/
for(q = M->col_head[j]; q->down && q->down->row < i; q = q->down)
p->down = q->down; q->down = p; /*完成插入*/
}
}
}
时间复杂度为O(t×s)
.t为稀疏矩阵中非零元素个数,s=max(m,n).