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

循环链表与静态链表

  • 导言
  • 一、循环链表
    • 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…

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

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

啊?这也算事务?!

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

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…

LLM提示词工程学习_Day01

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

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;填写表格 之后就可以正常使用了

智慧园区物联综合管理平台感知对象管理能力简述

物联感知对象管理, 不局限于物理传感设备, 还包括物联业务对象, 平台提供标准的设备建模能力以及标准的物联设备、 第三方物联系统SDK接入方案等; 实现对感知对象运行、 报警、 故障状态的反馈以及物联感知对象全生命周期信息管理。 基础定义配置 平台提供物联网目感知对…

Halcon纹理分析texture_laws/trans_from_rgb

Halcon纹理分析 文章目录 Halcon纹理分析1. 纹理滤波器2. 织物折痕检测 纹理是图像表面的一种灰度变化。有的纹理很规则&#xff0c;会以局部小区域为单元重复出现&#xff0c;而有的纹理则呈现出随机性。对于规则的纹理&#xff0c;可以很容易地从中分辨出重复的区域&#xff…

最新版 BaseRecyclerViewAdapterHelper4:4.1.2 最简单的QuickViewHolder用法,最简洁的代码,复制可用

为了照顾新手&#xff0c;尽量详细&#xff0c;高手勿喷&#xff01;&#xff01;&#xff01; 怕麻烦的话可以直接下载源码&#xff1a;https://download.csdn.net/download/ERP_LXKUN_JAK/88678044?spm1001.2014.3001.5503 先看文件结构&#xff0c;是不是很简单 AndroidSt…

【Pytorch】学习记录分享10——PyTorchTextCNN用于文本分类处理

【Pytorch】学习记录分享10——PyTorchTextCNN用于文本分类处理 1. TextCNN用于文本分类2. 代码实现 1. TextCNN用于文本分类 具体流程&#xff1a; 2. 代码实现 # coding: UTF-8 import torch import torch.nn as nn import torch.nn.functional as F import numpy as np…

【C++】STL 容器 - set 集合容器 ⑦ ( 查找元素 - set#find 函数 | 获取元素个数 - set#count 函数 )

文章目录 一、查找元素 - set#find 函数1、函数原型 简介2、代码示例 - set#find 函数 二、获取元素个数 - set#count 函数1、函数原型 简介2、代码示例 - set#find 函数 一、查找元素 - set#find 函数 1、函数原型 简介 在 C 语言的 STL 标准模板库 , std::set 集合容器 是一个…

数据分析硬核工具Origin各版本安装指南

下载链接 https://pan.baidu.com/s/12mENFtRFdNaLzVKmE6w_Uw?pwd0531 1.鼠标右击【Origin 2022(64bit)】压缩包&#xff08;win11及以上系统需先点击显示更多“选项”&#xff09;选择【解压到 Origin 2022(64bit)】。 2.双击打开解压后的【Origin 2022(64bit)】文件夹。 3.…

Python学习 - 爬虫系统架构设计

主要业务流程 初始请求请求过滤器请求队列响应下载器数据解析器数据清洗器存储器 设计图 master slave&#xff1a;master控制队列&#xff0c;过滤&#xff0c;传递任务&#xff1b;slave负责执行 缺点&#xff1a;master和slave端交互数据频繁&#xff0c;slave的数据进出…

图文证明 牛顿-莱布尼茨公式

牛顿-莱布尼茨公式 牛顿-莱布尼茨公式是微积分中的基本定理之一&#xff0c;它描述了函数的导数和不定积分之间的关系。 该公式通常用来计算定积分。设函数f(x)在区间[a, b]上连续&#xff0c;且F(x)是f(x)在该区间上的一个原函数 即F’(x) f(x)。则牛顿-莱布尼茨公式表示为&…

【AIGC-图片生成视频系列-2】八仙过海,各显神通:AI生成视频相关汇总剖析

最近「图片生成视频系列」层出不穷&#xff0c;我拜读并结合实践&#xff08;对&#xff0c;就是手撕代码&#xff0c;有开源就撕&#xff09;&#xff0c;并对以下几篇文章的相似点以及关键点稍微做个总结&#xff1a; 一. 生成视频中图像的一致性 在图像生成视频的这个过程…

提升CSC加分项|高职教师赴新西兰惠灵顿维多利亚大学访学交流

S老师科研背景条件一般&#xff0c;担心无法获得邀请函及通过CSC审批。我们建议&#xff1a;1.以加强国际合作和跨学科合作的方式&#xff0c;增强高职院校的影响力&#xff0c;为CSC评审提供加分项&#xff1b;2.同时申报4月份的国家公派和5月份的西部/地方合作项目&#xff0…

Java进阶(第八期): Java中递归的的使用和递归解决一些算法问题 Java中的异常机制、异常的处理逻辑 自定义异常

文章目录 一、递归1.1 递归的介绍1.2 递归的简单练习1.3 图解递归执行流程&#xff1a;1.4 使用递归完成悲波那契数列1.5 猴子吃桃子问题 二、异常三 、异常的处理逻辑3.1 try catch 捕获异常3.2 throws抛出异常 四、自定义异常 Java进阶&#xff08;第八期&#xff09; 一、递…