第6章串数组:稀疏矩阵的十字链表表示
6.7.3 十字链表
稀疏矩阵的三元组顺序表,是用顺序存储结构存放稀疏矩阵。此外,稀疏矩阵也可以链式存储结构表示。以如下稀疏矩阵为例:
M=[30050−1002000] \pmb{M}=\begin{bmatrix}3&0&0&5\\0&-1&0&0\\2&0&0&0\end{bmatrix} M=3020−10000500
如果要存储其中的非零元素,显然必须要说明该元素所在的行、列以及数值,也就是结点结构中应该有行、列和非零元素的值。但是,如果结点仅仅是这样的结构,还无法构成“链”,若要构成链式结构,必须要有“指针”指示当前非零元素的“后继”非零元素。矩阵中每个非零元的“后继”非零元,可能是与它在同一行、也有可能是在同一列。所以,每个结点还必须包括两个域:
- 向右域
right
:用以链接同一行中下一个非零元。 - 向下域:
down
:用以链接同一列中下一个非零元。
如图 6.7.3 所示,即为表示稀疏矩阵链式存储结构中的一个结点的结构示意图。
按照这种结点设计,矩阵 M\pmb{M}M 中的非零元素就可以表示为图 6.7.4 所示的存储方式,同一行的非零元通过 right
域链接成一个线性链表,同一列的非零元通过 down
域链接成一个线性链接,每个非零元既是某个行链表中的一个结点,又是某个列链表中的一个结点,整个矩阵构成了一个十字交叉的链表,故称这样的存储结构为十字链表。
在十字链表中的每个行链表和列链表,可以用之前学过的单链表或循环单链表表示。
每个单链表都有一个头指针,因此对于十字链表中的行链表和列链表,每个链表也都有一个头指针。于是,用两个一维数组,分别存储行链表的头指针和列链表的头指针,最终得到如图 6.7.5 所示的存储结构示意图。
按照上述示意图,就可以写出表示稀疏矩阵的十字链表的存储结构:
typedef struct OLNode{int i, j; //非零元的行下标、列下标ElemType e; //非零元数值struct OLNode * right, * down; //非零元所在行表和列表的后继链域
}OLNode; * OLink; //非零元结点typedef struct{OLink * rhead, *chead; //行和列链表头指针向量基址int mu, nu, tu; //稀疏矩阵的行数、列数和非零元个数
}CrossList;
根据图 6.7.5 可以知道,十字链表本质上就是以前所学过的链表的综合,因此,如果要将一个稀疏矩阵存储为十字链表的结构,也就是不断地向行链表或者列链表中插入新的非零元素所对应的结点。
Status CreateSMatrixOL(CrossList &M){//创建稀疏矩阵 M,采用十字链表存储表示if(M) free(M); //判断 M 是否已经存在,如果存在就不再创建scanf(&m, &n, &t); //输入 M 的行数、列数和非零元个数M.mu = m;M.nu = n;M.tu = t;if (!(M.rhead = (OLink *) malloc((m+1) * sizeof(OLink))))exit(OVERFLOW);if (!(M.chead = (OLink *) malloc((n+1) * sizeof(OLink))))exit(OVERFLOW);M.rhead[] = M.chead[] = NULL;//初始化行列头指针向量;各行列链表为空链表for (scanf(&i, &j, &e)); i != 0; scanf(&i, &j, &e){//按任意次序输入非零元if(!(p = (OLNode *)malloc(sizeof(OLNode))))exit(OVERFLOW);//生成结点p->i = i;p->j = j;p->e = e;if(M.rhead[i] == NULL || M.rhead[i]->j > j){//i 行链表空 //或输入的列号小于行表的首元结点的列下标//则该结点做插入到头指针后,即作为新的首元结点//此处使用单链表p->right = M.rhead[i];//M.rhead[i]为NULL或原首元结点M.rhead[i] = p;//头指针指向 p} else {//在行链表中适当位置插入for(q = M.rhead[i]; (q->right) && q->right->j < j; q = q->right){p->right = q->right;q->right = p;}//完成结点插入}//列链表中插入结点if(M.chead[j] == NULL || 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 OK;
}
【算法分析】
上述算法对非零元输入的先后次序没有任何要求。每建立一个非零元的结点,都要寻査它在行表和列表中的插入位置。若矩阵 M\pmb{M}M 为 m×nm\times nm×n ,且有 ttt 个非零元,则算法的时间复杂度为 O(t×max{m,n})O(t\times\max\{m,n\})O(t×max{m,n}) 。