【数据结构】链式家族的成员——循环链表与静态链表

循环链表与静态链表

  • 导言
  • 一、循环链表
    • 1.1 循环单链表
    • 1.2 循环双链表
  • 二、静态链表
    • 2.1 静态链表的创建
    • 2.2 静态链表的初始化
    • 2.3 小结
  • 结语

封面

导言

大家好!很高兴又和大家见面啦!!!

经过前面的介绍,相信大家对链式家族的成员——单链表与双链表的相关内容都已经熟练掌握了。前面我们重点介绍了通过C语言来实现单链表与双链表的一些基本操作,希望大家私下能够多多练习一下,帮助自己去吸收消化这些内容。

在今天的篇章中,我们要介绍的是线性表的链式存储另外两个成员——循环链表与静态链表,有了单链表与双链表的基础,相信大家应该能够很容易理解今天的内容。接下来我们就来一起看看吧!

一、循环链表

在前面介绍的单链表和双链表中,我们会发现,不管是单链表的表尾结点还是双链表的头结点和表尾结点,它们在创建好后指向的内容都是空指针,如下图所示:
单链表与双链表
正因为这种存储结构,导致我们在处理表头元素、表尾元素与表中元素时会有些许的差异,比如:

  • 在双链表中,我们采用后插法插入元素时,就需要判断该结点的后继结点是否为空指针;
  • 在单链表中,如果我们需要找到结点的前驱结点,我们只能通过从表头元素开始查找;

为了完善单链表与双链表的缺点,我们就可以将单链表与双链表做一个调整,如下所示:

循环链表

我们将单链表的表尾结点的指针域指向了头结点;
将双链表的表尾指针的后继指针指向了头结点,将双链表的头结点的前驱指针指向了表尾结点;

经过这个调整后我们会发现,此时的单链表和双链表都闭合起来了,这样闭合的链表我们将其称为循环链表。接下来我们就来分别介绍一下这两种循环链表相比于之前的改动;

1.1 循环单链表

循环单链表也就是表尾结点的指针域指向的是单链表的第一个结点,而头指针指向的也是单链表的第一个结点,所以我们可以认为,在循环单链表中,表尾结点的指针域指向的是头指针L。

正因为这个点所以在对循环单链表进行判空操作时我们就有了一个改动:

  • 由原先的判断头结点的指针域指向指向的是不是NULL,改为指向的是不是L;

用C语言来表示则是:

//循环单链表的判空
bool Empty(LinkList L)
{assert(L);//当指针L为空指针时报错if (L->next == L)return true;elsereturn false;
}

不管是单链表还是循环单链表,它们的插入、删除是一致的,唯一的区别就是,我们在对表尾结点的处理上会有差异:

  • 单链表的表尾结点的指针域判断指向的是NULL;
  • 循环单链表的表尾结点的指针域判断指向的是L;

用C语言来表式则是:

//循环链表的表尾结点判断
bool isTail(LinkList L,LNode* p)
{assert(L && p);//当指针L与指针p其中一个为空指针时报错if (p->next == L)return true;//当结点p的后继指针指向L时表明此时的结点p为表尾结点elsereturn false;//当它们不相等时表明此时的结点p不是表尾结点
}

我们在对单链表进行遍历时,只能是从头结点开始往后进行遍历,但是在循环链表中,我们可以从任意结点往后遍历,用C语言来表示的话我们则可以写成:

//循环链表的遍历
bool Ergodic(LNode* p)
{assert(p);//当p为空指针时报错LNode* r = p->next;//进行遍历的指针rwhile (r->next != p)//判断结点r的指针域是否指向结点p{r = r->next;//往后进行遍历}return true;//完成遍历返回true
}

在单链表中,我们要想从头结点找到表尾结点的话,我们需要从头开始进行遍历,此时的时间复杂度为O(n);但是在循环链表中,我们如果想通过表尾结点找到头结点的话,此时的时间复杂度则为O(1)。

由这个点,我们如果想对头结点或者表尾结点进行一些操作的话,我们则可以设置表尾指针r,这样我们就可以通过表尾指针来找到头指针,用C语言表示则是r->next即为头指针,这样我们要对表尾结点或者头结点进行插入或者删除元素的时间复杂度都是O(1);

注:通过设置表尾指针对头结点或者表尾结点完成插入或删除操作后,需要对指针r指向的内容进行修改。

1.2 循环双链表

循环双链表也就是表尾结点的后继指针指向了链表的第一个结点,而链表第一个结点的前驱指针指向了表尾结点。因此如果我们要对循环双链表进行判空操作时,我们只需要判断第一个结点的后继指针与前驱指针是否相等并且都等于头指针。

用C语言表示则是:

//循环双链表的判空操作
bool Empty(DLinkList L)
{assert(L);//当L为空指针时报错if (L->prior == L->next && L->prior == L)//判断前驱指针与后继指针是否都等于头指针return true;elsereturn false;
}

这里一定要注意如果仅仅判断头结点的前驱指针与后继指针相等的话,是不能确定是否为空表的,如下所示:

循环双链表
当双链表中有一个元素时,此时这个元素所在的结点既是表头结点又是表尾结点,因此在这种情况下循环双链表的头结点的前驱指针与后继指针都是指向这个结点的,所以在对循环双链表进行判空时一定要判断是否等于头指针;

循环双链表的其它变化与循环单链表类似,这里我就不再重复说明了,大家可以好好消化一下;

二、静态链表

静态链表我们可以理解为时顺序表与单链表的一个结合体。

静态链表是通过数组来描述线性表的链式存储结构,链表中的结点结构与单链表一致,都是由数据域与指针与构成;

但是不同的是,静态链表中的结点的指针域存储的是结点的相对地址,也就是在数组中的下标,这里我们将它称为游标,如下所示:
静态链表
由图可知,静态链表在内存中也是需要先申请一块连续的空间,对应的数组下标表示的是链表中的各个元素在物理位置上的关系,而游标表示的是链表中各个元素在逻辑上的关系。

在静态链表中,下标为0的元素被作为静态链表的头结点,它的数据域中可以不用存放信息,它的游标存放的是链表首元素的数组下标;

虽然静态链表是申请的一块连续的空间,但是表中的各个元素与单链表相同,不需要满足物理位置上相邻,只需要满足逻辑上相邻即可;

因此对于静态链表而言,它也是不能进行随机存取的,要访问各个元素的话只能通过从头结点开始往后访问;

2.1 静态链表的创建

我们要创建一个静态链表的话,我们就可以像创建一个静态顺序表一样,如下所示:

//静态链表的创建格式
#define MaxSize 10//静态链表的最大表长
typedef struct SLinkList {ElemType data;//数据域int next;//指针域——游标
}SLinkList[MaxSize];
//静态链表的类型为结构体数组类型
//SLinkList——重命名后的类型名
//MaxSize——链表的最大表长,不可修改
//SLinkList<==>struct SLinkList [MaxSize]
int main()
{SLinkList a;//定义一个静态链表astruct SLinkList b[MaxSize];//定义一个静态链表b//两种定义方式都是可以的return 0;
}

因为静态链表是通过数组实现的一个单链表,因此数组内的元素类型都是结构体类型,所以静态链表的实质是一个结构体数组。

这里对typedef的使用,实质上就是对数组类型的重命名的使用,有兴趣的朋友可以回看一下【C语言必学知识点五】指针中的typedef的使用,这里我有介绍通过typedef对函数指针类型进行重命名,这里的对数组类型进行重命名也是同理,如下所示:
数组类型重命名
我们在声明静态链表的数据类型时实质上是在声明一个结构体类型的数组,这里的静态链表类型定义等价于先定义一个结构体,再将该结构体对应的数组类型通过typedef重命名,如下所示:

//静态链表的创建
#define MaxSize 10//静态链表的最大表长
typedef struct SLinkList {ElemType data;//数据域int next;//指针域——游标
};//声明结构体类型
typedef struct SLinkList SLinkList[MaxSize];
//struct SLinkList[MaxSize]——数组类型
//通过typedef将数组类型重命名为SLinkList

这个内容我们就先介绍到这里,接下来我们来看一下静态链表的初始化;

2.2 静态链表的初始化

有看过【函数栈帧的创建与销毁】的朋友应该就会知道,我们在内存中申请空间时,申请的空间中会有一些初始的数据,这些初始数据如果我们将它们打印出来的会,会是一些随机的数据,因此为了避免我们创建的静态链表中存在这些随机值,所以我们要对其进行初始化。

由于游标存储的是各个元素的数组下标,数组的下标是从0开始依次递增,我们可以通过将表尾结点的游标设置为-1,来表示这个结点为表尾结点,同样的,我们在对其进行初始化时,可以将其设为-2,来表示此时的空间未被使用,如下所示:

//静态链表的初始化格式
bool InitSLinkList(SLinkList a)
{assert(a);//当a为空指针时报错for (int i = 0; i < MaxSize; i++){(a + i)->data = 0;//初始化数据域(a + i)->next = -2;//初始化游标}return true;
}

下面我们来测试一下初始化这个功能,这里我们还是以整型数据元素为例子,代码如下所示:

//静态链表的创建
#define MaxSize 10//静态链表的最大表长
typedef struct SLinkList {int data;//数据域int next;//指针域——游标
}SLinkList[MaxSize];
//静态链表的类型为结构体数组类型
//SLinkList——重命名后的类型名
//MaxSize——链表的最大表长,不可修改
//SLinkList<==>struct SLinkList [MaxSize]
//静态链表的初始化
bool InitSLinkList(SLinkList a)
{assert(a);//当a为空指针时报错for (int i = 0; i < MaxSize; i++){(a + i)->data = 0;//初始化数据域(a + i)->next = -2;//初始化游标}return true;
}
//打印静态链表
void Print_SLinkList(SLinkList a)
{printf("\n打印静态链表的各个元素的数据:>");for (int i = 0; i < MaxSize; i++)printf("%2d ", (a + i)->data);printf("\n打印静态链表的各个元素的游标:>");for (int i = 0; i < MaxSize; i++)printf("%2d ", (a + i)->next);printf("\n");
}
int main()
{SLinkList a;//定义一个静态链表astruct SLinkList b[MaxSize];//定义一个静态链表b//两种定义方式都是可以的if (InitSLinkList(a))//a为数组名,因此我们在传参时只需要传入数组名就可以了{Print_SLinkList(a);}return 0;
}

我们来看一下测试结果:
静态链表的初始化
当我们要对其进行插入或删除元素时,需要从头结点开始通过修改对应结点的游标来进行插入或者删除操作,这里我就不进行演示了,有兴趣的朋友可以自己下去试着编写一下对应的代码;

2.3 小结

对于静态链表,我们需要掌握以下内容:

  • 静态链表时通过数组实现的一个单链表;
  • 在静态链表中,下标为0的首元素作为静态链表的头结点,数据域中不需要存放任何内容;
  • 与静态顺序表一致,静态链表的大小是不可改变的;
  • 与单链表一致,静态链表不支持随机存取,只能从头结点开始往后查找;
  • 静态链表中的指针域存储的是下一个元素的数组下标;
  • 我们通过游标-1来表示链表的表尾结点;
  • 为了避免静态链表中未使用的空间的游标存储的是随机值,我们需要对其初始化为-2;
  • 静态链表的插入与删除操作与单链表的插入删除操作相同,只需要修改指针,不需要移动元素;
  • 静态链表适用于一些不支持指针的高级语言(如:Basic);
  • 静态链表还适用于数据元素数量固定不变的场景(如:操作系统中的文件分配表FAT);

结语

今天的内容到这里就全部结束了,有了顺序表、单链表与双链表这些知识点的基础,对于循环链表与静态链表的理解上就会相对容易一点,希望大家能够通过今天的内容强化对链表相关知识点的理解与使用。

在下一篇内容中,我们将对顺序表与链表的相关知识点做个回顾、对比与总结,大家记得关注哦!!!

最后感谢大家的翻阅,咱们下一篇再见!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/586343.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

软件测试/测试开发丨Mac Appium环境搭建

Mac 上 Appium 环境搭建 安装 nodejs 与 npm 安装方式与 windows 类似 &#xff0c;官网下载对应的 mac 版本的安装包&#xff0c;双击即可安装&#xff0c;无须配置环境变量。官方下载地址&#xff1a;https://nodejs.org/en/download/ 安装 appium Appium 分为两个版本&a…

【Transformer】深入理解Transformer模型1——初步认识了解

前言 Transformer模型出自论文&#xff1a;《Attention is All You Need》 2017年 近年来&#xff0c;在自然语言处理领域和图像处理领域&#xff0c;Transformer模型都受到了极为广泛的关注&#xff0c;很多模型中都用到了Transformer或者是Transformer模型的变体&#xff0…

ElasticSearch--基本操作

ElasticSearch 完成ES安装 http://101.42.93.208:5601/app/dev_tools#/console 库的操作 创建索引库 请求方式&#xff1a;PUT 请求路径&#xff1a;/索引库名&#xff0c;可以自定义 请求参数&#xff1a;mapping映射 PUT /test {"mappings": {"propertie…

计算机硬件 4.3显示器

第三节 显示器 一、基本概念 1.定义&#xff1a;将电信号转换为可以直接看到的图像的最基本输出设备。 2.分类&#xff1a;按显示色彩分&#xff1a;单色显示器、彩色显示器。 按显示原理分&#xff1a;CRT显示器、LCD显示器、LED显示器、OLED显示器。 3.原理结构&#xff…

【Oracle】 Oracle Sequence 性能优化

Sequence是很简单的&#xff0c;如果最大程度利用默认值的话&#xff0c;我们只需要定义sequence对象的名字即可。在序列Sequence对象的定义中&#xff0c;Cache是一个可选择的参数。默认的Sequence对象是有cache选项的&#xff0c;默认取值为20。这个默认值对于大多数情况下都…

云原生|kubernetes|kubernetes资源备份和集群迁移神器velero的部署和使用

前言&#xff1a; kubernetes集群需要灾备吗&#xff1f;kubernetes需要迁移吗&#xff1f; 答案肯定是需要的 那么&#xff0c;如何做kubernetes灾备和迁移呢&#xff1f;当然了&#xff0c;有很多的方法&#xff0c;例如&#xff0c;自己编写shell脚本&#xff0c;或者使用…

2023年江苏省职业院校技能大赛高职组“软件测试”赛项接口测试答案报告(含术语)

2023年江苏省职业院校技能大赛高职组“软件测试”赛项接口测试答案报告 接口测试要求: 1、执行接口测试 本部分按照软件接口测试文档要求,执行接口测试;使用接口测试工具PostMan,编写脚本、配置参数、执行接口测试并且截图。截图需粘贴在接口测试总结报告中。接口测试具体…

06-C++ 模板

模板、类型转换 模板 1. 简介 一种用于实现 通用编程 的机制。 将 数据类型 可以作为参数进行传递 。 通过使用模板&#xff0c;我们可以编写可复用的代码&#xff0c;可以适用于多种数据类型。 c模板的语法使用尖括号 < > 来表示泛型类型&#xff0c;并使用关键字…

啊?这也算事务?!

作者简介&#xff1a;大家好&#xff0c;我是smart哥&#xff0c;前中兴通讯、美团架构师&#xff0c;现某互联网公司CTO 联系qq&#xff1a;184480602&#xff0c;加我进群&#xff0c;大家一起学习&#xff0c;一起进步&#xff0c;一起对抗互联网寒冬 学习必须往深处挖&…

Java中的Optional类使用技巧

在Java中&#xff0c;Optional 是一个可以为null的容器对象。如果值存在则isPresent()方法返回true&#xff0c;调用get()方法会返回该对象。 使用Optional可以有效地防止NullPointerException。 下面是一些使用Optional的技巧&#xff1a; 创建Optional对象&#xff1a; Opt…

php-m和phpinfo之间不一致的问题的可能原因和解决办法

1.不同的 PHP配置文件: php -m 和 phpinfo 可能会使用不同的 PHP 配置文件。确保它们都使用相同的配置文件。你可以在命令行中使用 php --ini 来查找当前使用的配置文件位置&#xff0c;并在 phpinfo 中查看 Loaded Configuration File 来确保它们相同。 2.不同的 PHP 版本:确…

uni-app封装表格组件

组件代码&#xff1a; <template><view><uni-table class"tableBox" border stripe emptyText""><!-- 表头行 --><uni-tr class"tableTr"><uni-th :sortable"item.sortable" :class"item.sort…

【人工智能新闻】2023年人工智能热门新闻

欢迎收看我们的特别版时事通讯&#xff0c;重点报道“2023年人工智能热门新闻”今年是人工智能领域的里程碑&#xff0c;展示了重塑技术和我们日常生活的突破性进步和创新。从大型企业投资到革命性的技术发布&#xff0c;2023年的每个月都带来了非凡的成就。 加入我们&#xf…

什么是CAS,什么是AQS,两者到底有什么区别

首发2023-12-29 12:41yuan人生 CAS和AQS在java多线程编程中经常会涉及到&#xff0c;在许多面试的时候也会经常问到。但很多同学却不了解他们&#xff0c;那我们来一起看看什么是CAS&#xff0c;什么是AQS&#xff0c;两者到底有什么区别。 详见&#xff1a;https://www.touti…

用 Python 提取某一个公众号下的所有文章

当我们想要提取某一个公众号下的所有文章时&#xff0c;我们可以借助微信公众平台的开放接口&#xff0c;通过Python编写一个爬虫程序来实现。下面是一个示例代码&#xff0c;以及如何将其转化为一篇详细的微信公众号推文文章。 1. 导入所需库 首先&#xff0c;我们需要导入所…

LLM提示词工程学习_Day01

LLM提示词工程学习_Day01 安装学习环境基础Conda环境安装安装Python安装所需的包Jupyter Notebook 安装获取OpenAI API KEY&#xff0c;并写入工程目录里的.env文件进入Jupyter&#xff0c;先跑一段代码 安装学习环境 基础Conda环境安装 conda环境安装&#xff0c;miniconda也…

序列化机制以及应用场景

当谈到 Java 编程语言时&#xff0c;序列化机制是一个重要而又常见的概念。它允许对象在网络上传输或者保存到持久存储设备中。Java 提供了一个内建的序列化机制&#xff0c;它可以将对象转换为字节流&#xff0c;这个过程称为序列化&#xff0c;同时也能从字节流中重新构建对象…

go-carbon v2.3.1 发布,轻量级、语义化、对开发者友好的 Golang 时间处理库

carbon 是一个轻量级、语义化、对开发者友好的 golang 时间处理库&#xff0c;支持链式调用。 目前已被 awesome-go 收录&#xff0c;如果您觉得不错&#xff0c;请给个 star 吧 github.com/golang-module/carbon gitee.com/golang-module/carbon 安装使用 Golang 版本大于…

AI大模型:无需训练让LLM支持超长输入

显式搜索: 知识库外挂 paper: Unleashing Infinite-Length Input Capacity for Large-scale Language Models with Self-Controlled Memory System 看到最无敌的应用&#xff0c;文本和表格解析超厉害https://chatdoc.com/?viaurlainavpro.com ChatGPT代码实现: GitHub - ar…

【neo4j】desktop下载

【neo4j】desktop下载 https://neo4j.com/download/ 点击download&#xff0c;填写表格 之后就可以正常使用了