矩阵之间能够进行加法运算的前提条件是:各矩阵的行数和列数必须相等。
在行数和列数都相等的情况下,矩阵相加的结果就是矩阵中对应位置的值相加所组成的矩阵,例如:
图1 矩阵相加
十字链表法
之前所介绍的都是采用顺序存储结构存储三元组,在类似于矩阵的加法运算中,矩阵中的数据元素变化较大(这里的变化主要为:非0元素变为0,0变为非0元素),就需要考虑采用另一种结构——链式存储结构来存储三元组。
采用链式存储结构存储稀疏矩阵三元组的方法,称为“十字链表法”。
十字链表法表示矩阵
例如,用十字链表法表示矩阵 a ,为:
图2 矩阵用十字链表法表示
由此可见,采用十字链表表示矩阵时,矩阵的每一行和每一个列都可以看作是一个单独的链表,而之所以能够表示矩阵,是因为行链表和列链表都分别存储在各自的数组中
图 2 中:存储行链表的数组称为 rhead 数组;存储列链表的数组称为 chead 数组。
十字链表中的结点
从图2中的十字链表表示矩阵的例子可以看到,十字链表中的结点由 5 部分组成:
图3 十字链表中的结点
指针域a存储的是矩阵中结点所在列的下一个结点的地址(称为 “down域”);
指针域b存储的是矩阵中该结点所在行的下一个结点的地址(称为 “right域”);
用结构体自定义表示为:
typedef struct olnode { int i,j,e; //矩阵三元组 i 代表行 j 代表列 e 代表当前位置的数据 struct olnode *right,*down; //指针域 右指针 下指针 }olnode,*olink;
十字链表的结构
使用十字链表表示一个完整的矩阵,在了解矩阵中各结点的结构外,还需要存储矩阵的行数、列数以及非 0 元素的个数,另外,还需要将各结点链接成的链表存储在数组中。
所以,采用结构体自定义十字链表的结构,为:
typedef struct { olink *rhead,*chead; //存放各行和列链表头指针的数组 int mu,nu,tu; //矩阵的行数,列数和非零元的个数 }crosslist;
十字链表存储矩阵三元组
由于三元组存储的是该数据元素的行标、列标和数值,所以,通过行标和列标,就能在十字链表中唯一确定一个位置。
判断方法为:在同一行中通过列标来判断位置;在同一列中通过行标来判断位置。
首先判断该数据元素 a(例如三元组为:(i,j,k))所在行的具体位置:
如果 a 的列标 j 值比该行第一个非 0 元素 b 的 j 值小,说明该数据元素在元素 b 的左侧,这时 a 就成为了该行第一个非0元素(也适用于当该行没有非 0 元素的情况,可以一并讨论)
如果 a 的列标 j 比该行第一个非 0 元素 b 的 j 值大,说明 a 在 b 的右侧,这时,就需要遍历该行链表,找到插入位置的前一个结点,进行插入。
对应行链表的位置确定之后,判断数据元素 a 在对应列的位置:
如果 a 的行标比该列第一个非 0 元素 b 的行标 i 值还小,说明 a 在 b 的上边,这时 a 就成了该列第一个非 0 元素。(也适用于该列没有非 0 元素的情况)
反之,说明 a 在 b 的下边,这时就需要遍历该列链表,找到要插入位置的上一个数据元素,进行插入。
实现代码:
//创建系数矩阵m,采用十字链表存储表示 crosslist creatematrix_ol(crosslist m) { int m,n,t; int i,j,e; olnode *p,*q;//定义辅助变量 scanf("%d%d%d",&m,&n,&t); //输入矩阵的行列及非零元的数量 //初始化矩阵的行列及非零元的数量 m.mu=m; m.nu=n; m.tu=t; if(!(m.rhead=(olink*)malloc((m 1)*sizeof(olink)))||!(m.chead=(olink*)malloc((n 1)*sizeof(olink)))) { printf("初始化矩阵失败"); exit(0); //初始化矩阵的行列链表 } for(i=1;i<=m;i ) { m.rhead[i]=null; //初始化行 } for(j=1;j<=n;j ) { m.chead[j]=null; //初始化列 } for(scanf("%d%d%d",&i,&j,&e);0!=i;scanf("%d%d%d",&i,&j,&e)) //输入三元组 直到行为0结束 { if(!(p=(olnode*)malloc(sizeof(olnode)))) { printf("初始化三元组失败"); exit(0); //动态生成p } p->i=i; p->j=j; p->e=e; //初始化p if(null==m.rhead[i]||m.rhead[i]->j>j) { p->right=m.rhead[i]; m.rhead[i]=p; } else { for(q=m.rhead[i];(q->right)&&q->right->jright); 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->idown); p->down=q->down; q->down=p; } } return m; }
十字链表解决矩阵相加问题
在解决 “将矩阵 b 加到矩阵 a ” 的问题时,由于采用的是十字链表法存储矩阵的三元组,所以在相加的过程中,针对矩阵 b 中每一个非 0 元素,需要判断在矩阵 a 中相对应的位置,有三种情况:
提取到的 b 中的三元组在 a 相应位置上没有非 0 元素,此时直接加到矩阵 a 该行链表的对应位置上;
提取到的 b 中三元组在 a 相应位置上有非 0 元素,且相加不为 0 ,此时只需要更改 a 中对应位置上的三元组的值即可;
提取到的 b 中三元组在 a 响应位置上有非 0 元素,但相加为 0 ,此时需要删除矩阵 a 中对应结点。
提示:算法中,只需要逐个提取矩阵 b 中的非 0 元素,然后判断矩阵 a 中对应位置上是否有非 0 元素,根据不同的情况,相应作出处理。
设指针 pa 和 pb 分别表示矩阵 a 和矩阵 b 中同一行中的结点( pb 和 pa 都是从两矩阵的第一行的第一个非0元素开始遍历),针对上面的三种情况,细分为 4 种处理过程(第一种情况下有两种不同情况):
当 pa 结点的列值 j > pb 结点的列值 j 或者 pa == null (说明矩阵 a 该行没有非 0 元素),两种情况下是一个结果,就是将 pb 结点插入到矩阵 a 中。
当 pa 结点的列值 j < pb 结点的列值 j ,说明此时 pb 指向的结点位置比较靠后,此时需要移动 pa 的位置,找到离 pb 位置最近的非 0 元素,然后在新的 pa 结点的位置后边插入;
当 pa 的列值 j == pb 的列值 j, 且两结点的值相加结果不为 0 ,只需要更改 pa 指向的结点的值即可;
当 pa 的列值 j == pb 的列值 j ,但是两结点的值相加结果为 0 ,就需要从矩阵 a 的十字链表中删除 pa 指向的结点。
实现代码:
crosslist add**atrix(crosslist m,crosslist n){ olnode * pa,*pb;//新增的两个用于遍历两个矩阵的结点 olink * hl=(olink*)malloc(m.nu*sizeof(olink));//用于存储当前遍历的行为止以上的区域每一个列的最后一个非0元素的位置。 olnode * pre=null;//用于指向pa指针所在位置的此行的前一个结点 //遍历初期,首先要对hl数组进行初始化,指向每一列的第一个非0元素 for (int j=1; j<=m.nu; j ) { hl[j]=m.chead[j]; } //按照行进行遍历 for (int i=1; i<=m.mu; i ) { //遍历每一行以前,都要pa指向矩阵m当前行的第一个非0元素;指针pb也是如此,只不过遍历对象为矩阵n pa=m.rhead[i]; pb=n.rhead[i]; //当pb为null时,说明矩阵n的当前行的非0元素已经遍历完。 while (pb!=null) { //创建一个新的结点,每次都要复制一个pb结点,但是两个指针域除外。(复制的目的就是排除指针域的干扰) olnode * p=(olnode*)malloc(sizeof(olnode)); p->i=pb->i; p->j=pb->j; p->e=pb->e; p->down=null; p->right=null; //第一种情况 if (pa==null||pa->j>pb->j) { //如果pre为null,说明矩阵m此行没有非0元素 if (pre==null) { m.rhead[p->i]=p; }else{//由于程序开始时pre肯定为null,所以,pre指向的是第一个p的位置,在后面的遍历过程中,p指向的位置是逐渐向后移动的,所有,pre肯定会在p的前边 pre->right=p; } p->right=pa; pre=p; //在链接好行链表之后,链接到对应列的列链表中的相应位置 if (!m.chead[p->j]||m.chead[p->j]->i>p->i) { p->down=m.chead[p->j]; m.chead[p->j]=p; }else{ p->down=hl[p->j]->down; hl[p->j]->down=p; } //更新hl中的数据 hl[p->j]=p; }else{ //第二种情况,只需要移动pa的位置,继续判断pa和pb的位置,一定要有continue if (pa->jj) { pre=pa; pa=pa->right; continue; } //第三、四种情况,当行标和列标都想等的情况下,需要讨论两者相加的值的问题 if (pa->j==pb->j) { pa->e =pb->e; //如果为0,摘除当前结点,并释放所占的空间 if (pa->e==0) { if (pre==null) { m.rhead[pa->i]=pa->right; }else{ pre->right=pa->right; } p=pa; pa=pa->right; if (m.chead[p->j]==p) { m.chead[p->j]=hl[p->j]=p->down; }else{ hl[p->j]->down=p->down; } free(p); } } } pb=pb->right; } } //用于输出矩阵三元组的功能函数 display(m); return m; }
完整代码演示
#include#include typedef struct olnode { int i,j,e; //矩阵三元组i代表行 j代表列 e代表当前位置的数据 struct olnode *right,*down; //指针域 右指针 下指针 }olnode,*olink; typedef struct { olink *rhead,*chead; //行和列链表头指针 int mu,nu,tu; //矩阵的行数,列数和非零元的个数 }crosslist; crosslist creatematrix_ol(crosslist m); crosslist add**atrix(crosslist m,crosslist n); void display(crosslist m); void main() { crosslist m,n; printf("输入测试矩阵m:\n"); m=creatematrix_ol(m); printf("输入测试矩阵n:\n"); n=creatematrix_ol(n); m=add**atrix(m,n); printf("矩阵相加的结果为:\n"); display(m); } crosslist creatematrix_ol(crosslist m) { int m,n,t; int i,j,e; olnode *p,*q; scanf("%d%d%d",&m,&n,&t); m.mu=m; m.nu=n; m.tu=t; if(!(m.rhead=(olink*)malloc((m 1)*sizeof(olink)))||!(m.chead=(olink*)malloc((n 1)*sizeof(olink)))) { printf("初始化矩阵失败"); exit(0); } for(i=1;i<=m;i ) { m.rhead[i]=null; } for(j=1;j<=n;j ) { m.chead[j]=null; } for(scanf("%d%d%d",&i,&j,&e);0!=i;scanf("%d%d%d",&i,&j,&e)) { if(!(p=(olnode*)malloc(sizeof(olnode)))) { printf("初始化三元组失败"); exit(0); } p->i=i; p->j=j; p->e=e; if(null==m.rhead[i]||m.rhead[i]->j>j) { p->right=m.rhead[i]; m.rhead[i]=p; } else { for(q=m.rhead[i];(q->right)&&q->right->j 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->idown); p->down=q->down; q->down=p; } } return m; } crosslist add**atrix(crosslist m,crosslist n){ olnode * pa,*pb; olink * hl=(olink*)malloc(m.nu*sizeof(olink)); olnode * pre=null; for (int j=1; j<=m.nu; j ) { hl[j]=m.chead[j]; } for (int i=1; i<=m.mu; i ) { pa=m.rhead[i]; pb=n.rhead[i]; while (pb!=null) { olnode * p=(olnode*)malloc(sizeof(olnode)); p->i=pb->i; p->j=pb->j; p->e=pb->e; p->down=null; p->right=null; if (pa==null||pa->j>pb->j) { if (pre==null) { m.rhead[p->i]=p; }else{ pre->right=p; } p->right=pa; pre=p; if (!m.chead[p->j]||m.chead[p->j]->i>p->i) { p->down=m.chead[p->j]; m.chead[p->j]=p; }else{ p->down=hl[p->j]->down; hl[p->j]->down=p; } hl[p->j]=p; }else{ if (pa->j j) { pre=pa; pa=pa->right; continue; } if (pa->j==pb->j) { pa->e =pb->e; if (pa->e==0) { if (pre==null) { m.rhead[pa->i]=pa->right; }else{ pre->right=pa->right; } p=pa; pa=pa->right; if (m.chead[p->j]==p) { m.chead[p->j]=hl[p->j]=p->down; }else{ hl[p->j]->down=p->down; } free(p); } } } pb=pb->right; } } display(m); return m; } void display(crosslist m){ printf("输出测试矩阵:\n"); printf("m:\n---------------------\ni\tj\te\n---------------------\n"); for (int i=1;i<=m.nu;i ) { if (null!=m.chead[i]) { olink p=m.chead[i]; while (null!=p) { printf("%d\t%d\t%d\n",p->i,p->j,p->e); p=p->down; } } } }
运行结果:
输入测试矩阵m:
3 3 3
1 2 1
2 1 1
3 3 1
0 0 0
输入测试矩阵n:
3 3 4
1 2 -1
1 3 1
2 3 1
3 1 1
0 0 0
矩阵相加的结果为:
输出测试矩阵:
m:
---------------------
i j e
---------------------
2 1 1
3 1 1
1 3 1
2 3 1
3 3 1
总结
使用十字链表法解决稀疏矩阵的压缩存储的同时,在解决矩阵相加的问题中,对于某个单独的结点来说,算法的时间复杂度为一个常数(全部为选择结构),算法的整体的时间复杂度取决于两矩阵中非0元素的个数。