稀疏矩阵转十字链表

发布于:2022-11-08 ⋅ 阅读:(1120) ⋅ 点赞:(0)

定义:

十字链表(Orthogonal List)是有向图的另一种链式存储结构。该结构可以看成是将有向图的邻接表逆邻接表结合起来得到的。用十字链表来存储有向图,可以达到高效的存取效果。同时,代码的可读性也会得到提升。

● 特点

        • 只保存非零值

        • 为每一行设置一个单独链表 , 同时也为每一列设置一个单独链表


 


如上图, 所示 ,进而进行快速索引

● 用途 : 

 正如我们在一个矩阵里面找数据 , 告诉我们坐标 , 我们当然不会一行一行的去找 ,

我们会按行, 按列去寻找, 从而快速锁定目标

所以 , 存储结构也是如此 , 我们一个很长的链表 , 我们如果通过逐个遍历 , 那当然是不现实的

引论:       

这时候 ,有的同学会有疑问 ? 数组不就是可以直接寻找到目标的吗 ?


      我们之前已经知道数组是顺序存储的 , 我们根据坐标 和 起始地址 就知道要寻找的元素的位置 .

    然而, 我们的链表 是可以不存储在同一块空间的 , 可以随机存储  , 我们运用十字链表 ,为每一行 , 每一列进行编号 , 然后再逐个遍历 , 大大降低了寻找的时间复杂度.

数据结构的构思:

        所谓十字链 , 我们就是在之前我们单链表节点的基础上 , 增加了一个指针 , 然后我们就可以指向上下左右的节点了 , 就是如此

下面我们开始构建数据节点的结构

数据节点 :

我们可以分成三类:  


 头结点 :  

所谓头结点 就是 十字链表中起到标识作用的节点 ,(i 保存行数 , j 可以保存列数)


行/ 列头结点 :

        就是每行 / 每列的头节点 , 我们这里构建成循环链表 ,方便遍历插入


 

数据节点 :

          就是存储插入数据的节点 , 在原来基础上 , 增加了两个指针 ,一个指向右边的元素, 一个指向下面的元素 

十字链表节点数据结构构思 : 

我们之所以要创建不同的节点数据结构 ,就是因为不同的节点有不同的作用 , 所以我们根据我们的需要来进行相应的构建 ,可谓是随心所欲 , 看图构造 


头结点 :

我们既然要构建头结点 , 那就需要知道头结点的作用 , 头结点的作用就是 保存十字链表的信息 ,起标识作用 , 然后我们就需要定义 行数和列数的结构 ,

int row ;

int col;

头结点还要能够指向下一个队/列的指针域 ,

struct *link


行  / 列 头结点 :

       因为我们有很多行 , 很多列 ,并且我们要访问一个节点 , 就要访问列和行 ,所以我们需要为每个行/列节点设置序号 , 每个行 , 每个列都要链接到一起 ,所以我们要设置指针

*link        队列头结点链接

*rigth      指向右边一个数据元素

*down    指向下边一个数据元素

总结了上面的分析 , 我们下面开始具体分析数据节点的数据结构

我们观察头结点 和 队列头结点 ,他们很相似 , 都有 *down  , * right , link 指针 ,所以他们可以公用一个数据结构


观察数据节点 , 和 头结点结构  , 他们都有代表行 row 和列 col, 的结构 ,  尽管他们代表的意义可能有差别 ,但是结构都相似 , 只是其中不同的结构就是 , 数据结构有数据区, 头节点只有指向下一个行/列的指针域  , 所以 我们可以根据传入的数据 , 来进行对应的赋值

数据结构代码如下:

先定义行和列

# define M 3
# define N 4
//行数或者列数较大的数 ,作为最大头节点个数
# define Max ((M)>(N) ? (M):(N))

定义节点的共同数据

typedef struct mtxn
{
// 定义行,定义列
    int row;
    int col;
//定义节点指向右边和下边的指针
    struct mtxn *right,*down;      //指向节点的数据类型注意标清
//接下来,我们通过 union来通过传入的数据不同 , 来进行对相应类型的节点数据进行赋值
    union
    {    
        //传入数据是整数的话,判定为数据节点,进而进行对相应节点赋值
        int value;
        //传入数据是指针的话,判定为行/列头结点,进而构造头结点
        struct mtxn *link;
    }tag;
}MatNode;

相关数据的详细定义:

● 每个非零元素用一个节点表示 , 其中 i , j, tag.value 分别代表非零元素所在的行号,列号和相应的元素值;
● 头节点的i,j 代表总行数和总列数
● down 和 right 分别称为向下指针和向右指针,分别用来链接同列中和同行中的下一个非零元素节点.
● 作为头结点, tag.value 变为tag.link,代表头结点的链 . 

下面,为了我们后面构建十字链表 , 我们先假设已经创建好了十字链表 , 先尝试输出

 输出十字链表

我们观察上图,我们要遍历一行的话,需要跳转行头节点的右边节点,
当一行遍历完的时候,需要跳转列头节点的下一个节点
我们会发现,如果分开创建行/列节点的话,操作繁琐,并且定义麻烦
我们不如第i行和第i列都使用一个头节点定义,这样既方便了信息共享,又可以节省空间,简化操作


注意: 我们用一个节点,同时表示一列和一行


//传入十字链表
void DispMat(MatNode *hm)
{
	// p保存开始遍历的节点信息,q用来遍历一行节点
	MatNode *p,*q;
	//输出总行数和总列数
	printf("行=%d 列=%d\n", hm->row, hm->col);
	//刚开始,遍历的节点是头结点的后继节点,就是第一行,第一列的头结点
	p=hm->tag.link;
	//下面,在没有遍历回来的情况下,进行接着遍历
	//我们现在是行/列节点公用,所以遍历到头的话,也代表遍历完了
	while(p!=hm)
	{
		//刚开始动用right指针的话,就说明是遍历行
		q=p->right;
		//此时q已经接管p那一行的节点,开始遍历输出
		//在p没有遍历回来的情况下,接着运行
		while(p!=q)
		{
		//输出节点信息
		printf("%d\t%d\t%d\n",q->row,q->col,q->tag.value);
		//接着遍历行的下一个数据
		q=q->right
		}
		//出来的时候,代表此行已经遍历完了,开始将p接着指向下一个队列头结点
        //注意我们变换的指针数据类型即可
		p=p->tag.link;
	}
}

创建十字链表

刚才我们体验了一把输出十字链表的快感 , 接下来就需要完成我们对应的接口服务了 , 

根据需求, 来进行相应的构造 , 这也是我们设计代码的一种友好方式 .

观察输出特点: 

观察我们输出链表的模型 , 我们发现 , 我们的队列头结点是共用 行/列头结点的 , 这样的好处的是,简化操作 , 对同一个队列头节点 , 通过增加其指针的个数 ,来实现 共享行列数据 , 避免冗余 .

 观察队列的遍历方式 :

我们发现 我们是通过循环单链表来进行遍历的 , 通过遍历节点的信息 ,进行判断是否遍历完成

观察队列头结点的链接方式:

我们因为共用队列 行/列 头结点 , 所以只用链接一个循环单链表串即可 

观察数据节点的链接方式:

因为这是链表 , 所以数据链接 ,需要定义 *down指针 和 * right 指针 ,

插入的时候 , 需要尾插法插入

数据节点插入的位置:

插入固定行 ,固定列 ,所以需要定位特定的队列头结点  ,所以需要遍历 ,所以我们需要对队列头结点进行相应的标号

下面开始代码实操:

我们要构造十字链表,我们就回归最初的想法 
我们想把矩阵存放在链表里面 ,但是单链表查找困难,我们就想到一个办法,利用十字链表,通过行列头结点的跳跃寻址,进而快速查找
 

//下面开始构建
//传入矩阵,进行构造十字链表
void CreatMat(MatNode *&mh , ElemType a[][N])    //(要构造的十字链表指针地址,矩阵数组)
{
    //定义计数器
    int i,j;
    //定义十字链表指针数组,方便我们后续标号
    MatNode *h[Max];
    //定义新节点指针
    MatNode *p;
    //尾插法,保存每次节点插入信息的指针
    MatNode *q;
    //创建十字链表的头结点
    //采用尾插法创建头结点h1,h2,...,循环链表
    //创建节点
    //在行表中插入
    //在列表中插入
}

明天待续.....

 

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到