一,相关概念
㈠特殊矩阵:矩阵中存在大多数值相同的元,或非0元,且在矩阵中的分布有一定规律。
⒈对称矩阵:矩阵中的元素满足
aij=aji 1≤i,j≤n
⒉三角矩阵:上(下)三角矩阵指矩阵的下(上)三角(不包括对角线)中的元素均为常数c或0的n阶矩阵。
⒊对角矩阵(带状矩阵):矩阵中所有非0元素集中在主对角线为中心的区域中。
㈡稀疏矩阵:非0元素很少(≤ 5%)且分布无规律。
二,存储结构
1、对称矩阵
存储分配策略: 每一对对称元只分配一个存储单元,即只存储下三角(包括对角线)的元, 所需空间数为: n(n+1)/2。
存储分配方法: 用一维数组sa[n(n+1)/2]作为存储结构。
2、三角矩阵
也是一个n阶方阵,有上三角和下三角矩阵。下(上)三角矩阵是主对角线以上(下)元素均为零的n阶矩阵。设以一维数组sb[0..n(n+1)/2]作为n阶三角矩阵B的存储结构,仍采用按行存储方案,则B中任一元素bi,j和sb[k]之间仍然有如上的对应关系,只是还需要再加一个存储常数c的存储空间即可。如在下三角矩阵中,用n(n+1)/2的位置来存储常数
对特殊矩阵的压缩存储实质上就是将二维矩阵中的部分元素按照某种方案排列到一维数组中,不同的排列方案也就对应不同的存储方案
3、稀疏矩阵
常见的有三元组表示法、带辅助行向量的二元组表示法(也即行逻辑链表的顺序表),十字链表表示法等。
三、存储结构及C语言描述
1、三元组表示法
快速转置:按照a.data中三元组的次序进行转置,并将转置后的三元组放到b.data中的恰当位置。
恰当位置的确定:首先计算M矩阵的每一列(即T的每一行)中非0元的个数,然后求得M矩阵每一列第一个非0元在b.data中的位置。
三元组表示代码:
#include "stdio.h" #include "stdlib.h" #define MAXSIZE 12500 #define OK 1 typedef int ElemType; typedef struct { int i,j; ElemType e; }Triple; typedef struct { Triple data[MAXSIZE+1]; int mu,nu,tu; //矩阵行数,列数和非0元个数 }TSMatrix; int cpot[MAXSIZE+1],num[MAXSIZE+1]; int TransposeSMatrix(TSMatrix M,TSMatrix &T) { T.mu=M.nu; T.nu=M.mu; T.tu=M.tu; if(T.tu) { int q=1; for(int col=1;col<=M.nu;++col) for(int p=1;p<=M.tu;++p) if(M.data[p].j==col) { T.data[q].i=M.data[p].j; T.data[q].j=M.data[p].i; T.data[q].e=M.data[p].e; ++q; }//if }//if return OK; }//TransposeSMatrix int InPutM(TSMatrix &M) { printf("input nu mu tu(With a space interval)of a Matrix:\n"); scanf("%d %d %d",&M.nu,&M.mu,&M.tu); //row,colume,and tu printf("Please input the data of Matrix:\n"); for(int c=1;c<=M.tu;c++) { scanf("%d",&M.data[c].i); scanf("%d",&M.data[c].j); scanf("%d",&M.data[c].e); }//for return 1; }//InPut int PrintM(TSMatrix T) { printf("Matrix after transpose is:\n"); for(int c=1;c<=T.tu;c++) { printf("%d %d %d\n",T.data[c].i,T.data[c].j,T.data[c].e); }//for return 1; }//InPut int FastTransposeSMatrix(TSMatrix M,TSMatrix &T) { T.mu=M.nu; T.nu=M.mu; T.tu=M.tu; if(T.tu) { for(int col=1;col<=M.mu;++col) num[col]=0; for(int t=1;t<=M.tu;++t) ++num[M.data[t].j]; //记述M.data[t].j列 //非0元的个数 cpot[1]=1; //求第col列中第一个非零元在b.data(T)中的序号 for(int col=2;col<=M.mu;++col) cpot[col]=cpot[col-1]+num[col-1]; for(int p=1;p<=M.tu;++p) { int col=M.data[p].j; int q=cpot[col]; T.data[q].i=M.data[p].j; T.data[q].j=M.data[p].i; T.data[q].e=M.data[p].e; ++cpot[col]; }//for }//if return OK; }//FastTransposeSMatrix int main() { TSMatrix M,T; InPutM(M); //TransposeSMatrix(M,T); FastTransposeSMatrix(M,T); PrintM(T); return OK; }
2,行逻辑连接的顺序表
上面我们讨论的是稀疏矩阵的转置,现在我们讨论稀疏矩阵的相乘,相加,相减。如果预先知道不是稀疏矩阵,则用二维数组相乘法就可以了。如果是,则我们用来存储矩阵的结构称之为行逻辑连接的顺序表,就是加入一个行表来记录稀疏矩阵中每行的非零元素在三元组表中的起始位置。
带行逻辑链接的顺序表实现代码:
#include "stdio.h" #include "stdlib.h" #define MAXSIZE 12500 #define MAXRC 100 #define OK 1 #define ERROR -1 typedef int ElemType; typedef int Status; typedef struct { int i,j; ElemType e; }Triple; typedef struct { Triple data[MAXSIZE+1]; int rpos[MAXRC+1];//各行第一个非零元的位置表 int mu,nu,tu; //矩阵行数,列数和非0元个数 }RLSMatrix; int cpot[MAXSIZE+1],num[MAXSIZE+1]; Status CreateSMatrix(RLSMatrix &M) { // 创建稀疏矩阵M int i; Triple T; Status k; printf("请输入矩阵的行数,列数,非零元素数:(Separated by commas)\n"); scanf("%d,%d,%d",&M.mu,&M.nu,&M.tu); M.data[0].i=0; // 为以下比较做准备 for(i=1;i<=M.tu;i++) { do { printf("请按行序顺序输入第%d个非零元素所在的行(1~%d),列(1~%d),元素值:",i,M.mu,M.nu); printf("the 3 num Separated by commas\n"); scanf("%d,%d,%d",&T.i,&T.j,&T.e); k=0; if(T.i<1||T.i>M.mu||T.j<1||T.j>M.nu) // 行、列超出范围 k=1; if(T.i<M.data[i-1].i||T.i==M.data[i-1].i&&T.j<=M.data[i-1].j) // 没有按顺序输入非零元素 k=1; //&&优先级比||高 }while(k); // 当输入有误,重新输入 M.data[i]=T; } for(i=1;i<=M.tu;i++) // 计算rpos[] if(M.data[i].i>M.data[i-1].i) for(T.i=0;T.i<M.data[i].i-M.data[i-1].i;T.i++) M.rpos[M.data[i].i-T.i]=i; for(i=M.data[M.tu].i+1;i<=M.mu;i++) // 给最后没有非零元素的几行赋值 M.rpos[i]=M.tu+1; return OK; }//CreateSMatrix void DestroySMatrix(RLSMatrix &M) { // 销毁稀疏矩阵M(使M为0行0列0个非零元素的矩阵) M.mu=0; M.nu=0; M.tu=0; }//DestroySMatrix void PrintSMatrix(RLSMatrix M) { // 输出稀疏矩阵M int i; printf("%d行%d列%d个非零元素。\n",M.mu,M.nu,M.tu); printf("行 列 元素值\n"); for(i=1;i<=M.tu;i++) printf("%2d%4d%8d\n",M.data[i].i,M.data[i].j,M.data[i].e); for(i=1;i<=M.mu;i++) printf("第%d行的第一个非零元素是本矩阵第%d个元素\n",i,M.rpos[i]); }//PrintSMatrix Status MultSMatrix(RLSMatrix M,RLSMatrix N,RLSMatrix &Q) { // 求稀疏矩阵乘积Q=M*N。 int arow,brow,p,q,ccol,ctemp[MAXRC+1]; if(M.nu!=N.mu) // 矩阵M的列数应和矩阵N的行数相等 return ERROR; Q.mu=M.mu; // Q初始化 Q.nu=N.nu; Q.tu=0; M.rpos[M.mu+1]=M.tu+1; // 为方便后面的while循环临时设置 N.rpos[N.mu+1]=N.tu+1; if(M.tu*N.tu!=0) // M和N都是非零矩阵 { for(arow=1;arow<=M.mu;++arow) { //从M的第一行开始,到最后一行,arow是M的当前行 for(ccol=1;ccol<=Q.nu;++ccol) ctemp[ccol]=0; //Q的当前行的各列元素累加器清零 Q.rpos[arow]=Q.tu+1; //Q当前行的第1个元素位于上1行最后1个元素之后 for(p=M.rpos[arow];p<M.rpos[arow+1];++p) { // 对M当前行中每一个非零元 brow=M.data[p].j; //找到对应元在N中的行号(M当前元的列号) for(q=N.rpos[brow];q<N.rpos[brow+1];++q) { ccol=N.data[q].j; //乘积元素在Q中列号 ctemp[ccol]+=M.data[p].e*N.data[q].e; }//for } //求得Q中第arow行的非零元 for(ccol=1;ccol<=Q.nu;++ccol) //压缩存储该行非零元 if(ctemp[ccol]) { if(++Q.tu>MAXSIZE) return ERROR; Q.data[Q.tu].i=arow; Q.data[Q.tu].j=ccol; Q.data[Q.tu].e=ctemp[ccol]; }//if }//for }//if return OK; }//MultSMatrix int main() { RLSMatrix M,N,Q; CreateSMatrix(M); CreateSMatrix(N); MultSMatrix(M,N,Q); PrintSMatrix(Q); DestroySMatrix(M); DestroySMatrix(N); DestroySMatrix(Q); return OK; }
3,十字链表表示法
我们知道稀疏矩阵的三元组存储方式的实现很简单,每个元素有三个域分别是i, j, e。代表了该非零元的行号、列号以及值。那么在十字链表的存储方式下,首先这三个域是肯定少不了的,不然在进行很多操作的时候都要自己使用计数器,很麻 烦。而十字链表的形式大家可以理解成每一行是一个链表,而每一列又是一个链表。如图所示:
通过上面的图我们可以知道,每个结点不止要存放i, j, e。还要存放它横向的下一个结点的地址以及纵向的下一个结点的地址。形成一个类似十字形的链表的结构。那么每个结点的结构体定义也就呼之欲出了
这样我们对结点的插入与删除就要修改两个指针域。为了方便我们对结点的操作,我们要创建头指针或者头结点。至于到底是选择头指针呢还是头结点,请继续看下去..
我们想下要怎么创建头指针或者头结点,我们可以创建OLNode结构的结点形成一段连续的地址空间来指向某一行或者某一列中的结点(这是我最初的想法)
或者我们创建指针数组,数组元素中存放的地址就是某一行或者某一列的第一个结点的地址。来分析下两种方法
第一种方法会浪费大量的空间,而因为指针变量的空间都是4个字节,所以相对来说第二种节省空间。
毫无疑问我们选择第二种,也就是创建头指针。那么第二种我们用什么来实现?是数组还是动态内存分配?如果用数组我们要预先定义行和列的最大值,显然
这不是一个好主意,而动态内存分配的方法我们可以在用户输入了行数与列数之后分配相应的一段地址连续的空间。更为灵活,所以我们选择动态内存分配。
typedef struct { OLink *Rhead, *Chead; int mu, nu, tu; // 稀疏矩阵的行数、列数和非零元个数 }CrossList;
注意Rhead与Chead的类型,它们是指向指针的指针,也就是说,它们是指向我们定义的OLNode结构的结点的指针的指针。这话有点绕,不过相信C学的不错的朋友都应该清楚。如果不清楚的请看这个帖子 http://topic.csdn.net/u/20110829/01/506b33e3-ebc9-4905-bf8d-d0c877f85c08.html
现在结构体已经定义好了,我们来想想下面应该干什么。首先需要用户输入稀疏矩阵的行数与列数以及非零元的个数。那么就需要定义一个CrossList的结构体变量来存储这些值。
int main(void) { CrossList M; CreateSMatrix(&M); }
CreatSMatrix函数是我们今天要创建的函数,它用来建立稀疏矩阵并使用十字链表的方式存储矩阵。该函数的原型为int CreateSMatrix(CrossList *M);
当我们创建好了M就需要用户输入了,那么就要对用户的输入进行检查,看是否符合要求,首先mu, nu, tu都不能小于0,并且mu, nu不能等于0(我们这里假设行号与列号都是从1开始的,所以不能等于0),tu的值必须在0与mu * nu之间。
int CreateSMatrix(CrossList *M) { int i, j, m, n, t; int k, flag; ElemType e; OLNode *p, *q; if (M->Rhead) DestroySMatrix(M); do { flag = 1; printf("输入需要创建的矩阵的行数、列数以及非零元的个数"); scanf("%d%d%d", &m, &n, &t); if (m<0 || n<0 || t<0 || t>m*n) flag = 0; }while (!flag); M->mu = m; M->nu = n; M->tu = t; ................................... return 1; }
当用户输入了正确的值以后,我们要创建头指针的数组
这里m+1与n+1是为了后面操作的方便使得它们的下标从1开始。注意我们创建时候的强制转换的类型。OLink * , 首先它是指针类型。我们连续创建了m+1个,每一个都指向OLink类型的变量,所以它里面存放的就应该是一个指向OLNode类型的指针的地址。
创建完以后必须初始化,因为我们后面的插入就其中一个判断条件就是它们的值为NULL,也就是该行或者该列中没有结点
现在我们就可以进行结点的输入了,显而易见的要输入的结点个数刚才已经存放到了t变量中,那么我们要创建t个结点。这就是一个大的循环
而每创建一个结点我们都要修改它的两个指针域以及链表头数组。那么我们可以分开两次来修改,第一次修改行的指针域,第二次修改列的指针域。
do { flag = 1; printf("输入第%d个结点行号、列号以及值", k); scanf("%d%d%d", &i, &j, &e); if (i<=0 || j<=0) flag = 0; }while (!flag); p = (OLink) malloc (sizeof(OLNode)); if (NULL == p) exit(-1); p->i = i; p->j = j; p->e = e;
当用户输入一系列正确的值,并且我们也创建了一个OLNode类型的结点之后我们要讲它插入到某一行中。首先要确定插入在哪一行?我们输入的时候已经输入了行号i,那么我们自然要插入到i行中,那么应该怎样去插入?分两种情况
1、当这一行中没有结点的时候,那么我们直接插入
2、当这一行中有结点的时候我们插入到正确的位置
逐个来分析:
怎么判定一行中有没有结点? 记得我们前面对Rhead的初始化吗? 所有的元素的值都为NULL,所以我们的判断条件就是 NULL==M->Rhead[i].
现在我们来解决第二个问题。
怎么去找到要插入的正确位置。当行中有结点的时候我们无非就是插入到某个结点之前或者之后。那么我们再回到前面,在我们定义Rhead的时候就说 过,某一行的表头指针指向的就是该行中第一个结点的地址。我们假设该行中已经有了一个结点我们称它为A结点,如果要插在A结点之前那么A结点的列号必定是 大于我们输入的结点(我们称它为P结点)的列号的。我们的插入操作就要修改头指针与p结点的right域。就像链表中的插入。那么当该行中没有结点的时候 我们怎么去插入?同样是修改头指针让它指向我们的P结点,同样要修改P结点的right域。看,我们可以利用if语句来实现这两种条件的判断。那么就有了 下面的代码!
if(NULL==M->Rhead[i] || M->Rhead[i]->j>j) { // p插在该行的第一个结点处 // M->Rhead[i]始终指向该行的第一个结点 p->right = M->Rhead[i]; M->Rhead[i] = p; }
现在我们再想一下怎么去插入到某一个结点的后面? 我们新创建的P结点要插入到现有的A结点的后面,那么P的列号必定是大 于A的列号,那么我们只要找到第一个大于比P的列号大的结点B,然后插入到B结点之前!如果现有的结点没有一个结点列号是大于P结点的列号的,那么我们就 应该插入到最后一个结点之后!所以我们首先要寻找符合条件的位置进行插入
for(q=M->Rhead[i]; q->right && q->right->j < j; q=q->right) ; p->right=q->right; // 完成行插入 q->right=p;
这样就插入完成了,至于列的指针域的修改和这个类似!现在贴出所有代码
int CreateSMatrix(CrossList *M) { int i, j, m, n, t; int k, flag; ElemType e; OLNode *p, *q; if (M->Rhead) DestroySMatrix(M); do { flag = 1; printf("输入需要创建的矩阵的行数、列数以及非零元的个数"); scanf("%d%d%d", &m, &n, &t); if (m<0 || n<0 || t<0 || t>m*n) flag = 0; }while (!flag); M->mu = m; M->nu = n; M->tu = t; //创建行链表头数组 M->Rhead = (OLink *)malloc((m+1) * sizeof(OLink)); if(!M->Rhead) exit(-1); //创建列链表头数组 M->Chead = (OLink *)malloc((n+1) * sizeof(OLink)); if(!(M->Chead)) exit(-1); for(k=1;k<=m;k++) // 初始化行头指针向量;各行链表为空链表 M->Rhead[k]=NULL; for(k=1;k<=n;k++) // 初始化列头指针向量;各列链表为空链表 M->Chead[k]=NULL; //输入各个结点 for (k=1; k<=t; ++k) { do { flag = 1; printf("输入第%d个结点行号、列号以及值", k); scanf("%d%d%d", &i, &j, &e); if (i<=0 || j<=0) flag = 0; }while (!flag); p = (OLink) malloc (sizeof(OLNode)); if (NULL == p) exit(-1); p->i = i; p->j = j; p->e = e; if(NULL==M->Rhead[i] || M->Rhead[i]->j>j) { // p插在该行的第一个结点处 // M->Rhead[i]始终指向它的下一个结点 p->right = M->Rhead[i]; M->Rhead[i] = p; } else // 寻查在行表中的插入位置 { //从该行的行链表头开始,直到找到 for(q=M->Rhead[i]; q->right && q->right->j < j; q=q->right) ; p->right=q->right; // 完成行插入 q->right=p; } if(NULL==M->Chead[j] || M->Chead[j]->i>i) { p->down = M->Chead[j]; M->Chead[j] = p; } else // 寻查在列表中的插入位置 { //从该列的列链表头开始,直到找到 for(q=M->Chead[j]; q->down && q->down->i < i; q=q->down) ; p->down=q->down; // 完成行插入 q->down=p; } } return 1; }