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

循环链表与静态链表

  • 导言
  • 一、循环链表
    • 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;
	else
		return false;
}

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

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

用C语言来表式则是:

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

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

//循环链表的遍历
bool Ergodic(LNode* p)
{
	assert(p);//当p为空指针时报错
	LNode* r = p->next;//进行遍历的指针r
	while (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;
	else
		return 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;//定义一个静态链表a
	struct 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;//定义一个静态链表a
	struct SLinkList b[MaxSize];//定义一个静态链表b
	//两种定义方式都是可以的
	if (InitSLinkList(a))//a为数组名,因此我们在传参时只需要传入数组名就可以了
	{
		Print_SLinkList(a);
	}
	return 0;
}

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

2.3 小结

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

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

结语

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

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

企业如何做好内容?媒介盒子分享

在个性化算法的支持下&#xff0c;企业通过创作优质内容使消费者主动选择企业的时代已经来临&#xff0c;对于中小企业来说&#xff0c;这是能够低成本进行营销的好机会。但是有许多企业对内容的理解依旧是片面的&#xff0c;今天媒介盒子就来和大家聊聊&#xff1a;企业如何做…

【MYSQL】-函数

&#x1f496;作者&#xff1a;小树苗渴望变成参天大树&#x1f388; &#x1f389;作者宣言&#xff1a;认真写好每一篇博客&#x1f4a4; &#x1f38a;作者gitee:gitee✨ &#x1f49e;作者专栏&#xff1a;C语言,数据结构初阶,Linux,C 动态规划算法&#x1f384; 如 果 你 …

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

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

zookeeper之集群搭建

1. 集群角色 zookeeper集群下&#xff0c;有3种角色&#xff0c;分别是领导者(Leader)、跟随着(Follower)、观察者(Observer)。接下来我们分别看一下这三种角色的作用。 领导者(Leader)&#xff1a; 事务请求&#xff08;写操作&#xff09;的唯一调度者和处理者&#xff0c;保…

LTSpice仿真场效应管(FET)的方法

刚开始用LTSpice学习电子电路&#xff0c;发现添加 JFET 和 MOSFET 的方法与添加普通原件不一样&#xff0c;需要分两步完成。 第一步&#xff1a;选择元件 njf、pjf、nmos、pmos&#xff0c;分别对应 N Channel 的 JFET 和 P Channel 的 JFET&#xff1b;N Channel 的 MOSFET…

SpringMVC学习与开发(四)

注&#xff1a;此为笔者学习狂神说SpringMVC的笔记&#xff0c;其中包含个人的笔记和理解&#xff0c;仅做学习笔记之用&#xff0c;更多详细资讯请出门左拐B站&#xff1a;狂神说!!! 11、Ajax初体验 1、伪造Ajax 结果&#xff1a;并未有xhr异步请求 <!DOCTYPE html> &…

组合总和[中等]

一、题目 给你一个 无重复元素 的整数数组candidates和一个目标整数target&#xff0c;找出candidates中可以使数字和为目标数target的 所有 不同组合 &#xff0c;并以列表形式返回。你可以按 任意顺序 返回这些组合。candidates中的 同一个 数字可以 无限制重复被选取 。如果…

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

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

『番外篇六』SwiftUI 取得任意视图全局位置的三种方法

概览 在 SwiftUI 开发中,利用描述性代码我们可以很轻松的构建各种丰富多彩的视图。我们可以设置它们的大小、位置、颜色并应用不计其数的修改器。 但是,小伙伴们是否想过在 SwiftUI 中如何获取一个视图的全局位置坐标呢? 在本篇博文中,您将学到如下内容: 概览1. SwiftU…

【docker实战】01 Linux上docker的安装

Docker CE是免费的Docker产品的新名称&#xff0c;Docker CE包含了完整的Docker平台&#xff0c;非常适合开发人员和运维团队构建容器APP。 Ubuntu 14.04/16.04&#xff08;使用 apt-get 进行安装&#xff09; # step 1: 安装必要的一些系统工具 sudo apt-get update sudo ap…

java虚拟机内存管理

文章目录 概要一、jdk7与jdk8内存结构的差异二、程序计数器三、虚拟机栈3.1 什么是虚拟机栈3.2 什么是栈帧3.3 栈帧的组成 四、本地方法栈五、堆5.1 堆的特点5.2 堆的结构5.3 堆的参数配置 六、方法区6.1 方法区结构6.2 运行时常量池 七、元空间 概要 根据 JVM 规范&#xff0…

20231229在Firefly的AIO-3399J开发板的Android11使用挖掘机的DTS配置单前后摄像头ov13850

20231229在Firefly的AIO-3399J开发板的Android11使用挖掘机的DTS配置单前后摄像头ov13850 2023/12/29 11:10 开发板&#xff1a;Firefly的AIO-3399J【RK3399】 SDK&#xff1a;rk3399-android-11-r20211216.tar.xz【Android11】 Android11.0.tar.bz2.aa【ToyBrick】 Android11.…

【软件工程】走进瀑布模型:传统软件开发的经典之路

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a; 软件工程 ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 前言&#xff1a; 正文 主要阶段&#xff1a; 优点&#xff1a; 缺点&#xff1a; 应用范围&#xff1a; 结语 我的其他博客 前言&am…

算法训练营Day26

#Java #全排列 #回溯 开源学习资料 Feeling and experiences&#xff1a; 递增子序列&#xff1a;力扣题目链接 给你一个整数数组 nums &#xff0c;找出并返回所有该数组中不同的递增子序列&#xff0c;递增子序列中 至少有两个元素 。你可以按 任意顺序 返回答案。 数组…

需求分析 :不得不重新去面对的一关。

软件需求分析 背景 深入需求产生的背景明确项目目标了解用户群体 需求优先级 需求的分类与整理明确需求优先级让团队成员都参与到需求分析中来&#xff0c;增加团队合作能力与效率 编写需求文档 整理好的需求编写成详细的需求文档包括需求的描述、输入/输出格式、功能流程…

【数据库系统概论】第7章-数据库设计

文章目录 7.1 数据库设计概述7.2 需求分析7.2.1 需求分析的任务7.2.2 需求分析的难点7.2.2 需求分析的方法7.2.3 数据字典 7.3 概念结构设计7.3.1 概念模型7.3.2 E-R模型7.3.3 概念结构设计 7.4 逻辑结构设计7.4.1 E-R图向关系模型的转换7.4.2 数据模型的优化7.4.3 设计用户子模…

电子学会C/C++编程等级考试2023年03月(八级)真题解析

C/C++等级考试(1~8级)全部真题・点这里 第1题:最短路径问题 平面上有n个点(n<=100),每个点的坐标均在-10000~10000之间。其中的一些点之间有连线。 若有连线,则表示可从一个点到达另一个点,即两点间有通路,通路的距离为两点间的直线距离。现在的任务是找出从一点到另…

技术探秘:在RISC Zero中验证FHE——RISC Zero应用的DevOps(2)

1. 引言 前序博客&#xff1a; 技术探秘&#xff1a;在RISC Zero中验证FHE——由隐藏到证明&#xff1a;FHE验证的ZK路径&#xff08;1&#xff09; 技术探秘&#xff1a;在RISC Zero中验证FHE——由隐藏到证明&#xff1a;FHE验证的ZK路径&#xff08;1&#xff09; 中&…

Vue : 监视属性

目录 一个案例 监听属性 handler immediate vm.$watch(xxx) 深度监视 监视的简写 computed和watch之间的区别 一个案例 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport"…

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

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