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

循环链表与静态链表

  • 导言
  • 一、循环链表
    • 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.xdnf.cn/news/823800.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

Java介绍

Java 是一门纯粹的面向对象编程语言&#xff0c;它吸收了C的各种优点&#xff0c;还努力摒弃了C里难以理解的多继承、指针等概念&#xff0c;真正地实现了面向对象理论&#xff0c;因而具有功能强大和简单易用两个特征。 除了基础语法之外&#xff0c;Java还有许多必须弄懂的特…

oracle-存储结构

文件包括 控制文件.ctl、数据文件.dbf、日志文件.log这三类放在存储上。 参数文件&#xff1a;空间的划分&#xff0c;进程的选用&#xff08;.ora&#xff09; oracle启动的时候需要读一下&#xff0c;数据库启动后&#xff0c;参数文件并不关闭&#xff0c;但即使文件丢了&a…

2-4基础算法-离散化/贪心/01背包问题

文章目录 一.离散化二.贪心01背包问题 一.离散化 离散化是一种将数组的值域压缩&#xff0c;从而更加关注元素的大小关系的算法。 离散化数组要求内部有序&#xff08;一般去重&#xff09; 可以通过离散化下标得到值 也可以通过值得到离散化下标 #include <iostream>…

DataFunSummit:2023年数据湖架构峰会-核心PPT资料下载

一、峰会简介 现今&#xff0c;很多企业每天都有PB级的数据注入到大数据平台&#xff0c;经过离线或实时的ETL建模后&#xff0c;提供给下游的分析、推荐及预测等场景使用。面对如此大规模的数据&#xff0c;无论是分析型场景、流批一体、增量数仓都得益于湖仓一体等数据湖技术…

《微信小程序开发从入门到实战》学习六十七

6.6 网络API 部分小程序服务端不是用云开发技术实现&#xff0c;而是由开发人员使用后端开发语言实现。 在小程序用网络API与&#xff08;开发人员使后端开发语言建设的&#xff09;服务端进行交互&#xff0c;可与服务端交换数据、上传或下载文件。 6.6.1 服务器域名配置 …

数字资产学习笔记

附&#xff1a;2023年数据资源入表白皮书下载&#xff1a; 关注WX公众号&#xff1a; commindtech77&#xff0c; 获得数据资产相关白皮书下载地址 1. 回复关键字&#xff1a;数据资源入表白皮书 下载 《2023数据资源入表白皮书》 2. 回复关键字&#xff1a;光大银行 下载 光…

openGauss学习笔记-179 openGauss 数据库运维-逻辑复制-发布订阅

文章目录 openGauss学习笔记-179 openGauss 数据库运维-逻辑复制-发布订阅179.1 发布179.2 订阅179.3 冲突处理179.4 限制179.5 架构179.6 监控179.7 安全性179.8 配置设置179.9 快速设置 openGauss学习笔记-179 openGauss 数据库运维-逻辑复制-发布订阅 发布和订阅基于逻辑复…

AJAX: 整理2:学习原生的AJAX,这边借助express框架

1. npm install express 终端直接安装 2. 测试案例&#xff1a;Hello World&#xff01; 新建一个express.js的文件&#xff0c;写入下方的内容 // 1. 引入express const express require(express)// 2. 创建服务器 const app express()// 3.创建路由规则 // request 是对请…

C#使用switch语句更改窗体颜色

目录 一、示例 二、生成 用switch多路选择语句及窗体的BackColor属性更改窗体的BackColor属性。该属性用于获取或设置控件的背景颜色。 可以使用Color结构的静态属性获取Color对象&#xff0c;如Color.Red&#xff1b;也可以使用Color结构的静态方法Color.FromArgb()&#xf…

windows 安装multipass

安装说明 Multipass orchestrates virtual Ubuntu instances Launch an instance (by default you get the current Ubuntu LTS) multipass launch --name foo 下载 Multipass orchestrates virtual Ubuntu instances 安装 执行安装exe 前提 需要安装hyper-V 参考链接 …

goframe v2 模板引擎的用法

这里用的goframe v2框架 提醒&#xff1a;下面的import 引入的控制器和api&#xff0c;根据自己实际项目路径 main函数 import ("context""github.com/gogf/gf/v2/net/ghttp""github.com/gzdzh/dzhgo/modules/dzhCms/controller/web""gith…

海凌科HLK-V2语音识别模块更新词条

简介 HLK-V20 是海凌科的离线语音识别模块, 中英文不同时支持, 只支持中文/英文, 具体识别看每次的SDK更新设置;资料下载 可以在微信公众包搜索海凌科或HI-LINK, 下载资料 感知模块->HLK-V20 模块限制 中英文被限制, 需要根据你在官网设置的SDK信息进行确定;可以仅设置3…

JVM知识总结(简单且高效)

1. JVM内存与本地内存 JVM内存&#xff1a;受虚拟机内存大小的参数控制&#xff0c;当大小超过参数设置的大小时会报OOM。本地内存&#xff1a;本地内存不受虚拟机内存参数的限制&#xff0c;只受物理内存容量的限制&#xff1b;虽然不受参数的限制&#xff0c;如果所占内存超过…

【设计模式】状态模式

文章目录 引例状态模式理论状态模式代码优化结合享元模式并发问题解决 策略模式 VS 状态模式 引例 交通信号灯系统的设计与实现 方案一 传统设计方案 定义交通灯颜色的枚举 public enum LightColor { Green,Red,Yellow }交通灯类TrafficLight&#xff0c;处理颜色转换等业务…

IP地理位置定位技术基本原理

IP地理位置定位技术的基本原理是基于IP地址的特性。每个IP地址在网络中都有一个与之对应的地理位置信息&#xff0c;这是通过IP地址数据库来确定的。这个数据库由ISP&#xff08;Internet Service Provider&#xff09;或其它一些机构维护&#xff0c;其中包含了每个IP地址的地…

【LeetCode】修炼之路-0001-Two Sum(两数之和)【python】【简单】

前言 计算机科学作为一门实践性极强的学科,代码能力的培养尤为重要。当前网络上有非常多优秀的前辈分享了LeetCode的最佳算法题解,这对于我们这些初学者来说提供了莫大的帮助,但对于我这种缺乏编程直觉的学习者而言,这往往难以消化吸收。&#xff08;为什么别人就能想出这么优雅…

迷宫问题的对比实验研究(代码注释详细、迷宫及路径可视化)

题目描述 对不同的迷宫进行算法问题&#xff0c;广度优先、深度优先、以及人工智能上介绍的一些算法&#xff1a;例如A*算法&#xff0c;蚁群算法等。 基本要求&#xff1a; &#xff08;1&#xff09;从文件读入9*9的迷宫&#xff0c;设置入口和出口&#xff0c;分别采用以上方…

懒加载的el-tree中没有了子节点之后还是有前面icon箭头的展示,如何取消没有子节点之后的箭头显示

没有特别多的数据 <template><el-tree:props"props":load"loadNode"lazyshow-checkbox></el-tree></template><script>export default {data() {return {props: {label: name,children: zones,isLeaf:"leaf",//关…

华为服务器安装银河麒麟V10操作系统(IBMC安装)

iBMC是华为面向服务器全生命周期的服务器嵌入式管理系统。提供硬件状态监控、部署、节能、安全等系列管理工具&#xff0c;标准化接口构建服务器管理更加完善的生态系统。 服务器BMC IP&#xff1a;192.168.2.100 一、准备工作 1、确保本机和服务器BMC管理口在同一网络 2、银…

时序分解 | Matlab实现贝叶斯变化点检测与时间序列分解

时序分解 | Matlab实现贝叶斯变化点检测与时间序列分解 目录 时序分解 | Matlab实现贝叶斯变化点检测与时间序列分解效果一览基本介绍程序设计参考资料 效果一览 基本介绍 Matlab实现贝叶斯变化点检测与时间序列分解 1.Matlab实现贝叶斯变化点检测与时间序列分解&#xff0c;完…