稀疏矩阵转十字链表

这篇具有很好参考价值的文章主要介绍了稀疏矩阵转十字链表。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

定义:

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

● 特点

        • 只保存非零值

 文章来源地址https://www.toymoban.com/news/detail-432097.html

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


稀疏矩阵转十字链表稀疏矩阵转十字链表

 


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

● 用途 : 

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

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

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

引论:       

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


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

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

数据结构的构思:

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

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

数据节点 :

我们可以分成三类:  

稀疏矩阵转十字链表


 头结点 :  

所谓头结点 就是 十字链表中起到标识作用的节点 ,(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])    //(要构造的十字链表指针地址,矩阵数组)
{
	一 .构造十字链表头结点
	二 .采用尾插法链接头结点 ,形成循环链表
	三 .创建数据节点
	四 .在行表中插入
	五 .在列表中插入
}

创建十字链表 头节点

//为头结点分配空间

mh = (MatNode *)malloc(sizeof(MatNode));

//头节点的行数,列数赋值

mh->row = M;
mh->col = N;

二 .采用尾插法链接头结点 ,形成循环链表

我们要考虑后续数据节点,按照行列快速寻找插入位置 , 我们把每个队列头结点 ,按照行列顺序 , 依次放在数组里面 ,这样我们就可以快速查找到行列头结点 , 然后进行后续的遍历插入


//定义存储队列头结点的数据,方便快速寻找

MatNode *h[Max];

//采用尾插法,我们首先定义尾指针,初始指向头结点

MatNode *r;

r = mh;

//接下来通过为每个行列头结点分配空间,创建节点,然后链接
for(i = 0; i<Max; i++)
{
    //为行/列头结点分配空间
    h[i] = (MatNode *)malloc(sizeof(MatNode));
    //刚开始创建节点的时候,节点都指向自身,构成循环链表

  //注意: 此时链接的是每一行,每一列的队列头结点的down, right 指针,刚开始都指向自身,

  //后续会和数据节点 构 成循环链表 
    h[i]->down = h[i]->right = h[i];

//此时链表的队列头结点 ,要注意区分
    r->tag.link = h[i];
    r = h[i];
}

//队列头结点链接完后, 为了构成循环链表 ,方便后续遍历输出 , 所以尾结点指向 头结点 mh
r->tag.link = mh;

// 注意 : 我们每行 , 每列 的 行 / 列头结点 , 都是运用一个队列 头结点 ,好处的方便数据共享 ,方便查找 , 

只需要多增加两个指针 , 就可以管理 插入行列数据节点了 , 所以上面我们只链接了一组队列头结点 

也能构成逻辑上的十字链表 


稀疏矩阵转十字链表

 

开始构建数据节点 ,然后插入到行表和列表
//注意行表和列表 , 我们只需要根据插入的坐标进行找到对应的行列 ,然后再进行遍历查找插入即可
int i,j;
MatNode *h[Max];
MatNode *p,*q,*r;
for(i = 0; i<M; i++)
{
    for(j = 0; j < N; j++)
    {
        //寻找非零节点
        if(a[i][j]!=0)
        {
            //创建节点,包含数据区和坐标
            p = (MatNode *)malloc(sizeof(MatNode));
            p->row = i;
            p->col = j;
            p->tag.value = a[i][j];
            //创建完,就可以进行插入了
            //先在行表中插入
            //先找到具体的行号
            q = h[i];
            //然后进行寻找对应的列号
            //此时我们进行思考, 我们要插入到特定列,特定行的位置上

             //见下图分析:

            while(q->right != h[i] && q->right->col<j)            
            {
                q = q->right;
            }
            p->right = q->right;
            q->right = p; //完成行表的插入
            //在列表中插入
            q = h[j];
            while(q->down !=h[i] && q->down->row<i)
            {
                q = q->down;
            }
            p->down = q->down;
            q->down = p;

            

        }
    }
}


我们的数据节点 ,如何插入到特定的位置上呢 ? 

我们首先观察行链表 , 我们可以通过 数据h [ i ] 来找到特定的行 , 然后接下来 , 我们就需要在行列表的基础上进行插入了 , 

此时是一个链表

稀疏矩阵转十字链表

行坐标已经找到了 ,接下来 我们要插入的位置就是列坐标为 j 的位置 ,  

首先我们要遍历这个链表 ,直到循环链表遍历完, 回到原点 

while (q->right != h[ i ] )

然后我们要插入的地方是 第 j 列 ,因为不是每个节点都有数据节点  ,所以我们按顺序插入的话 , 需要插入到 (i , j)左边非零节点的后面

按顺序插入的话, 我们要插入的位置(i,j) 此时右边是没有链表的(因为是按顺序,还没有插入),所以插入的位置 也就是链表的末尾位置

所以我们进行往右遍历 ,遍历到末尾 , 直到数据节点的列小于 j 的最后一个元素 ,并且 此数据节点的下一个元素就指向队列头结点 ,就跳出 

while (q->right != h[ i ] && q->right->col < j)

{

        q = q->right;

}

//跳出的时候, q指向我们要插入的位置的前一个链表数据元素

//接下来进行插入即可

//把尾指针先交给新节点的后继指针 right

p -> right = q->right;

//接着把 新节点的位置指向 新节点

q -> right = p;        

//完成行表的插入

//注意: 上面两步,步骤不能调换 ,避免覆盖指针

列表中插入,也是同理: 不一一赘述,

评价 :

       ● 十字链表 中每一个非零元素同时包含在所在行的行链表中 , 和所在列的列链表中 , 大大降低了链表的长度, 方便了算法中行方向和列方向的搜索 ,因而大大降低了算法的时间复杂度 .

稀疏矩阵转十字链表

 

延伸 : 

用十字链表的思维解决问题

对于二维集合 ,当其中有不同数量的集合的时候 ,我们就可以用十字链表来进行链表 ,进而进行分层查找

稀疏矩阵转十字链表

 


数据节点的结构定义:

typedef struct dnode
{
	Elemtype data;
	struct dnode *next;
}DType;

定义头节点的结构:

typedef struct hnode
{
	struct hnode *link;
	//定义后继指针的类型 ,就是DType
	DType *next;
}HType;

接下来 ,我们进行相应的链接头结点 ,然后链接数据节点即可 ,进行遍历集合的时候 ,注意构建集合的数据结构即可 

这里只做拓展 ,不做详细构造 .

十字链表完整代码如下:

#include <stdio.h>
#include <malloc.h>
#define M 3                     //矩阵行
#define N 3                     //矩阵列
#define Max ((M)>(N)?(M):(N))   //矩阵行列较大者
typedef int ElemType;
typedef struct mtxn
{
    int row;                    //行号
    int col;                    //列号
    struct mtxn *right,*down;   //向右和向下的指针
    union
    {
        ElemType value;
        struct mtxn *link;
    } tag;
} MatNode;          //十字链表类型定义

void CreatMat(MatNode *&mh,ElemType a[][N])
{
    int i,j;
    MatNode *h[Max],*p,*q,*r;
    mh=(MatNode *)malloc(sizeof(MatNode));//创建十字链表的头节点
    mh->row=M;
    mh->col=N;
    r=mh;                   //r指向尾节点
    for (i=0; i<Max; i++)       //采用尾插法创建头节点h1,h2,…循环链表
    {
        h[i]=(MatNode *)malloc(sizeof(MatNode));
        h[i]->down=h[i]->right=h[i];        //将down和right方向置为循环的
        r->tag.link=h[i];                   //将h[i]加到链表中
        r=h[i];
    }
    r->tag.link=mh;                         //置为循环链表
    for (i=0; i<M; i++)                     //处理每一行
    {
        for (j=0; j<N; j++)                 //处理每一列
        {
            if (a[i][j]!=0)                 //处理非零元素
            {
                p=(MatNode *)malloc(sizeof(MatNode));   //创建一个新节点
                p->row=i;
                p->col=j;
                p->tag.value=a[i][j];
                q=h[i];                         //查找在行表中的插入位置
                while (q->right!=h[i] && q->right->col<j)
                    q=q->right;
                p->right=q->right;
                q->right=p; //完成行表的插入
                q=h[j];                         //查找在列表中的插入位置
                while (q->down!=h[j] && q->down->row<i)
                    q=q->down;
                p->down=q->down;
                q->down=p;      //完成列表的插入
            }
        }
    }
}
void DispMat(MatNode *mh)
{
    MatNode *p,*q;
    printf("行=%d  列=%d\n", mh->row,mh->col);
    p=mh->tag.link;
    while (p!=mh)
    {
        q=p->right;
        while (p!=q)        //输出一行非零元素
        {
            printf("%d\t%d\t%d\n", q->row,q->col,q->tag.value);
            q=q->right;
        }
        p=p->tag.link;
    }
}
//本主程序用于调试
int main()
{
    ElemType a[M][N]= {{1,0,3},{0,2,0},{0,0,5}};
    ElemType b[M][N]= {{-1,0,2},{0,-2,0},{1,0,-5}};
    MatNode *mx,*my;
    CreatMat(mx,a);
    CreatMat(my,b);
    printf("a的十字链表:\n");
    DispMat(mx);
    printf("b的十字链表:\n");
    DispMat(my);
    return 0;
}

 

 

到了这里,关于稀疏矩阵转十字链表的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【数据结构】数组和字符串(八):稀疏矩阵的链接存储:十字链表的创建、插入元素、遍历打印(按行、按列、打印矩阵)、销毁

    【数据结构】数组和字符串(一):矩阵的数组表示   矩阵是以按行优先次序将所有矩阵元素存放在一个一维数组中。但是对于特殊矩阵,如对称矩阵、三角矩阵、对角矩阵和稀疏矩阵等, 如果用这种方式存储,会出现大量存储空间存放重复信息或零元素的情况,这样会造

    2024年02月06日
    浏览(55)
  • 西工大NOJ数据结构理论——013.以十字链表为存储结构实现矩阵相加(严5.27)

      我第一下拿到这个题目,第一反应就是先 定义好数据结构 ,然后构建好十字链表基础操作的函数,也就是“ 创插遍历 ”这些操作。下面是我的定义和函数操作。 typedef int ElemType; typedef struct OLNode{     int i,j;//行号和列号     ElemType elem;     struct OLNode *right;//右边元素 

    2023年04月26日
    浏览(86)
  • 数据结构——十字链表

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

    2024年02月05日
    浏览(41)
  • 图的存储结构——十字链表

    目录 引入(为何存在?) 数据结构分析 十字链表的示意图: 代码实现(以有向网为例,创建十字链表)         数据结构部分:        算法实现部分:         测试部分:(以图8.14为例) 时间与空间复杂度分析分析:         回忆邻接矩阵与邻接表的存储结构,它

    2024年02月04日
    浏览(47)
  • 图的存储结构-十字链表

    十字链表 (Orthogonal List)是有向图的一种链式存储结构。可以看成是将有向图的邻接表和逆邻接表结合起来得到的一种链表。在十字链表中,对应于有向图中每一条弧有一个结点,对应于每个顶点也有一个结点。 顶点结点 弧结点 十字链表结点: 在弧结点中有5个域:其中尾

    2024年02月05日
    浏览(48)
  • 【数据结构】十字链表的画法

    有向边又称为弧 假设顶点 v 指向 w,那么 w 称为弧头,v 称为弧尾 顶点节点采用顺序存储 顶点节点 data:存放顶点的信息 firstin:指向以该节点为终点(弧头)的弧节点 firstout:指向以该节点为起点(弧尾)的弧节点 弧节点 tailvex:起点(弧尾)在数组中的索引 headvex:终点(

    2024年02月11日
    浏览(49)
  • 图论:十字链表的基本概念理解

    写博客是为了学习理解更深刻         在学习甲鱼课的十字链表时稍稍有点懵,进C站查找一番后发现也没有比较亲近小白的描述和解释。抱着让自己理解更深刻以及与大家分享的态度写下了这篇博客。如有错误请指出。         邻接表的使用简单方便,但在对有向图的处

    2023年04月08日
    浏览(39)
  • 数据结构--5.1图的存储结构(十字链表、邻接多重表、边集数组)

    目录 一、十字链表(Orthogonal List) 二、邻接多重表 三、边集数组 四、深度优先遍历   重新定义顶点表结点结构:  data firstIn firstOut 重新定义边表结构结点: tailVex headVex headLink tailLink        十字链表的好处就是因为把邻接表和逆邻接表整合在了一起,这样既容易找到Vi为

    2024年02月10日
    浏览(43)
  • 稀疏矩阵的运算——矩阵相加

    南昌航空大学实验报告 课程名称:    数据结构A   实验名称:        实验五    稀疏矩阵运算        班   级:      XXX           学生姓名:       XXX         学号:      XXXXX       指导教师评定:       XXX     签    名:       XXX    

    2024年02月03日
    浏览(43)
  • 数据结构— 数组、特殊矩阵、稀疏矩阵

    💟作者简介:大家好呀!我是 路遥叶子 ,大家可以叫我 叶子 哦! ❣️     📝个人主页:【路遥叶子的博客】 🏆博主信息: 四季轮换叶 , 一路招摇胜!      专栏 【数据结构-Java语言描述】  【安利Java零基础】 🐋希望大家多多支持😘一起进步呀!~❤️ 🌈若有帮助

    2024年02月02日
    浏览(53)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包