C语言实现单链表和双向循环链表

news2024/12/26 11:01:46

全文目录

  • 链表
  • 单链表实现
    • 申请节点
    • 头插尾插
    • 头删尾删
    • 任意节点后插入删除
    • 单链表的销毁
  • 带头双向循环链表实现
    • 链表初始化
    • 申请节点
    • 头插尾插
    • 头删尾删
    • 任意节点后插入删除
    • 链表的销毁
  • 链表和顺序表对比总结

链表

概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。

链表的实现主要由一下几种形式:

  1. 是否带头节点(哨兵位)
  2. 是否单向
  3. 是否循环

单链表就是单向

链表的结构虽然多样,但是最常用的还是两种结构:

  1. 单链表

结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。

  1. 带头双向循环链表

结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了。

其中可以看出链表的结构:

// 单链表的节点
typedef struct SListNode 
{
	SLDataType data;	// 节点数据
	struct SListNode* next; 	// 下一个节点的位置
	struct SListNode* pre;	// 双向链表特有
} SListNode;

链表的接口实现:

  1. 申请节点
  2. 头插头删
  3. 尾插尾删
  4. 在任意节点后插入删除

单链表实现

单链表是链表最简单的实现,其他形式的链表都是在单链表的基础上实现的,所以用来练手最合适。同时基本上链表的OJ题都是以单链表的形式出现的,其他的很多数据结构都是以单链表为子结构实现的。

申请节点

新申请的节点的next 指针是指向NULL

// 动态申请一个节点
SListNode* BuySListNode(SLDataType x) 
{
	SListNode* newSListNode = (SListNode*)malloc(sizeof(SListNode));   // 开辟一个新节点
	newSListNode->data = x;		// 新节点,保存新数据
	newSListNode->next = NULL;		// 先将新节点的next指向NULL,如果有需要自行更改,可以防止野指针的使用
	return newSListNode;
}

头插尾插

插入节点都需要注意空链表的时候需要改变链表头结点的值,所以需要传入头节点的地址

// 单链表的头插
void SListPushFront(SListNode** pplist, SLDataType x) 
{
	assert(pplist);		// 就算plist为NULL,pplist也不能是空指针。
	SListNode* newSListNode = BuySListNode(x);		// 开辟新节点
	assert(newSListNode);
	
	newSListNode->next = *pplist;		// 新的头结点指向老的头结点
	*pplist = newSListNode;		// 改变头结点
}

在这里插入图片描述

// 单链表尾插
// 由于可能改变头结点的值,所以需要将头节点的地址传过来
// 但是不用判断是不是空指针,因为空指针的时候也是需要插入的
void SListPushBack(SListNode** pplist, SLDataType x) 
{
	assert(pplist);		// 就算plist为NULL,pplist也不能是空指针。
	SListNode* newSListNode = BuySListNode(x);		// 开辟新节点
	assert(newSListNode);
	
	if (*pplist == NULL) 
	{
		*pplist = newSListNode;		// 当链表中没有数据的时候直接改变头结点的值
		return;
	}
	
	SListNode* tail = *pplist;		// 使用局部变量存档头结点的值
	while (tail->next != NULL) {		// 找最后一个节点
		tail = tail->next;		// 进入下一个节点
	}
	tail->next = newSListNode;		// 添加新节点
}

在这里插入图片描述

头删尾删

删除节点两点注意事项:

  1. 空链表时不能删
  2. 只有一个节点时,需要 改变头结点的值
  3. 删除节点之前需要改变前一个节点的指向
// 单链表头删
void SListPopFront(SListNode** pplist) 
{
	assert(pplist);		// 就算plist为NULL,pplist也不能是空指针。
	assert(*pplist);
	SListNode* temp = *pplist;		// 保存头结点的位置,防止找不到头结点
	*pplist = (*pplist)->next;		// 头结点改为头结点下一个
	free(temp);			// 释放原头结点
}

在这里插入图片描述

// 单链表的尾删
void SListPopBack(SListNode** pplist) 
{
	assert(pplist);		// 就算plist为NULL,pplist也不能是空指针。
	assert(*pplist);
	SListNode* tail = *pplist;    // 使用局部变量存档头结点
	if (tail->next == NULL) 
	{		// 如果只有一个节点,直接释放头结点
		free(tail);
		*pplist = NULL;
		return;
	}
	
	while (tail->next->next != NULL) 
	{		// 寻找尾结点前一个节点
		tail = tail->next;
	}
	free(tail->next);		// 释放尾结点
	tail->next = NULL;		// 更改尾结点
}

在这里插入图片描述

任意节点后插入删除

// 单链表在pos位置之后插入x
void SListInsertAfter(SListNode* pos, SLDataType x) 
{
	assert(pos);
	SListNode* newSListNode = BuySListNode(x);		// 创建新节点
	newSListNode->next = pos->next;		// 插入新节点
	pos->next = newSListNode;
}

在这里插入图片描述

// 单链表删除pos位置之后的节点
void SListEraseAfter(SListNode* pos) 
{
	assert(pos->next && pos);		// 防止当前节点为空,或者下一个节点为空
	SListNode* temp = pos->next;		// 保存当前位置下一个节点的位置
	pos->next = pos->next->next;		// 当前节点指向下一个的下一个节点
	free(temp);
}

在这里插入图片描述

单链表的销毁

链表都是动态申请的空间,需要及时释放

// 单链表的销毁
void SListDestory(SListNode* plist)
{
	assert(plist);

	SListNode* cur = plist;

	while (cur)
	{
		SListNode* del = cur;
		cur = cur->next;
		free(del);
	}
}

带头双向循环链表实现

带头双向循环链表可以说是链表的最终形态,除了不能随机访问,基本上没有缺点。

结构:

typedef int LTDataType;
typedef struct ListNode 
{
	LTDataType data;
	struct ListNode* _prev;
	struct ListNode* _next;
} ListNode;

链表初始化

就算是没有空链表也会有一个哨兵位

// 创建返回链表的头结点.
ListNode* ListCreate() 
{
	ListNode* phead = (ListNode*)malloc(sizeof(ListNode));   // 创建头结点
	assert(phead);
	phead->_prev = phead;		
	phead->_next = phead;
	return phead;
}

在这里插入图片描述

申请节点

新节点的前后指针都是指向NULL

// 创建新节点
ListNode* CreatListNode(LTDataType x) 
{
	ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));
	assert(newNode);
	newNode->data = x;
	newNode->_next = NULL;
	newNode->_prev = NULL;
	return newNode;
}

头插尾插

// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x) 
{
	assert(pHead);
	ListNode* newNode = CreatListNode(x);
	// 注意不能将一个头结点的下一个的下一个节点弄丢了,需要先处理头结点的下一个节点和新节点的关系
	newNode->_next = pHead->_next;
	pHead->_next->_prev = newNode;
	newNode->_prev = pHead;
	pHead->_next = newNode;
}

在这里插入图片描述

// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x) 
{
	assert(pHead);
	ListNode* newNode = CreatListNode(x);
	// 注意赋值顺序,不可将尾结点弄丢了,需要先处理尾结点新节点的关系
	pHead->_prev->_next = newNode;
	newNode->_prev = pHead->_prev;
	newNode->_next = pHead;
	pHead->_prev = newNode;
}

在这里插入图片描述

头删尾删

// 双向链表头删
void ListPopFront(ListNode* pHead) 
{
	assert(pHead);
	assert(pHead->_next != pHead);   // 防止空链表时的删除
	// 删除前需要先将链表的头结点和删除节点的下一个节点链接起来
	ListNode* del = pHead->_next;
	del ->_next->_prev = pHead;
	pHead->_next = del->_next;
	free(del);
}

在这里插入图片描述

// 双向链表尾删
void ListPopBack(ListNode* pHead) 
{
	assert(pHead);
	assert(pHead->_next != pHead);		// 防止空链表时的删除
	// 删除前需要将删除节点的前一个节点和头结点
	ListNode* del = pHead->_prev;
	pHead->_prev = del->_prev;
	del->_prev->_next = pHead;
	free(del);
}

在这里插入图片描述

任意节点后插入删除

这里的图根前面插入删除的图都是一样的

// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x) 
{
	assert(pos);
	ListNode* newNode = CreatListNode(x);
	// 不可将pos的前一个节点弄丢了,需要先处理pos的前一个节点和新节点的关系
	pos->_prev->_next = newNode;
	newNode->_prev = pos->_prev;
	newNode->_next = pos;
	pos->_prev = newNode;
}


// 双向链表删除pos位置的节点
void ListErase(ListNode* pos) 
{
	assert(pos);
	pos->_prev->_next = pos->_next;
	pos->_next->_prev = pos->_prev;
	free(pos);
}

链表的销毁

// 双向链表销毁
void ListDestory(ListNode* pHead) {
	assert(pHead);
	ListNode* cur = pHead->_next;
	while (cur != pHead) {
		ListNode* next = cur->_next;
		free(cur);
		cur = next;
	}
	free(pHead);
}

链表和顺序表对比总结

链表与顺序表的优缺点都是相互呼应的:

在这里插入图片描述

其中的缓存利用率,在CPU和内存中有着三级缓存,每次读取数据都是先将内存中的数据放到缓存中,再让CPU读取,但不是一个数据一个数据地放,而是一段一段地放。由于顺序表物理地址空间连续的优势,缓存的利用率也就高了,而链表大概率每次都需要重新再内存中读取数据,就不能保证缓存的利用率。

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

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

相关文章

GO语言篇之发布开源软件包

GO语言篇之发布开源软件包 文章目录 GO语言篇之发布开源软件包新建仓库拉取到本地初始化项目编写代码提交代码发布引用软件包 我们写GO语言程序的时候难免会引用第三方的软件包,那么你知道别人是怎么发布自己的软件包吗,别急,这篇博客教你怎么…

Apache DolphinScheduler 如何实现自动化打包+单机/集群部署?

Apache DolphinScheduler 是一款开源的分布式任务调度系统,旨在帮助用户实现复杂任务的自动化调度和管理。DolphinScheduler 支持多种任务类型,可以在单机或集群环境下运行。下面将介绍如何实现 DolphinScheduler 的自动化打包和单机/集群部署。 自动化…

【Oracle】数据库导入导出

Oracle数据库导入导出 文章目录 Oracle数据库导入导出一、expdp导出1、管理员身份登录2、删除以前测试的用户及对应的数据3、创建表空间(源表--待导出的表)4、创建用户,给用户设置默认表空间和临时表空间5、给用户授权(创建表和视…

Unity+百度文心大模型驱动AI小姐姐数字人

1.简述 最近看到新闻,说是百度、字节、商汤、百川、智普等几家企业及机构所发布的生成式大语言模型,通过了《生成式人工智能服务管理暂行办法》,成为首批获得官方备案的大语言模型服务提供商。虽然一直在使用包括文心一言、chatglm这些大语言…

怒赞了,阿里P8面试官推荐的Java高并发核心编程文档

前言 学完阿里P8面试官推荐的Java高并发核心编程文档后,终于拿到了蚂蚁p6的offer,这份文档包含的内容有点多。 Java高并发核心编程文档《尼恩Java高并发三部曲》获读者怒赞!获取方式见文末 文章目录 前言尼恩Java高并发三部曲卷1&#xff1…

适合引流的运动步数打卡抽奖小程序源码开发

要健康也要瘦?那么有一个可以让你悄悄改变还可以获取奖品的小程序简直不要太入心。用运动步数兑换奖品,每天运动一下,换点小礼品,简直不要太惬意。 运动步数兑换小程序核心亮点: 小程序与微信运动做了关联&#xff…

Android环境配置笔记

文章目录 一、各环境文档二、参考 一、各环境文档 Gradle官方的兼容性文档:Java Compatibility 更新日期:2023.9.12 Android Gradle插件版本:Android Gradle Plugin 二、参考 参考文章:Android问题记录

SS928搭建NNN环境

环境要求:ubuntu18.04 参考文件: 《ATC工具使用指南》《应用开发指南》《驱动和开发环境安装指南》 《昇腾模型压缩工具使用指南(ONNX)》 交叉编译器的安装-----------------------------------------------------------------…

C语言“牵手”淘宝商品评论数据方法,淘宝商品评论接口,淘宝商品评价接口,淘宝API接口申请指南

淘宝商品评论API是淘宝开放平台为开发者提供的一套应用程序编程接口,通过该接口,开发者可以获取到店铺所有商品的评价数据。 淘宝商品评论API包含以下接口: taobao.item.reviews.get:用于获取指定商品的评价数据,输入…

凯迪正大— 氧化锌避雷器检测仪

一、概述 RBZ-3B氧化锌避雷器直流参数测试仪是专门用于检测10kV及6KV电力系统用无间隙氧化锌避雷器MOA阀电间接触不良的内部缺陷,根据《电力设备预防性试验规程》DL/T596-1996中14.2的规定,发电厂、变电所在每年雷雨季前和必要时应该对金属氧化物避雷器…

基于ssm的商场管理信息系统的设计与实现

基于ssm的商场管理信息系统的设计与实现 前言 这个项目适合初学者熟悉框架的项目系统,前端框架采用layui全新回归版本2.8,界面更加丝滑。需要的记得扣我发源码哦! 项目脑图 项目技术 前端技术:layui框架,JavaScrip…

USB适配器应用芯片 国产GP232RL软硬件兼容替代FT232RL DPU02直接替代CP2102

USB适配器,是英文Universal Serial Bus(通用串行总线)的缩写,而其中文简称为“通串线”,是一个外部总线标准,用于规范电脑与外部设备的连接和通讯。是应用在PC领域的接口技术, 移动PC由于没有电池,电源适配…

如何在群晖中,正确配置 docker 的 ipv6 地址

参考 2023年9月12日 https://synocommunity.com/ https://github.com/wangliangliang2/fix_synology_docker_ipv6 https://post.smzdm.com/p/an3np8m7/ 正文 关于这个话题,国内搜索引擎得到的结果出奇的一致,且过时。 (看的我脑壳痛&#…

神经反馈设备使用感受2:采集Muse的EEG原始数据(转自知乎)

神经反馈设备使用感受2:采集Muse的EEG原始数据 转自知乎,内容很好,怕之后找不到 想了一下,单写一部分来介绍一下Muse在数据采集方面的操作。同时也解释一下我自己的EEG数据是从哪里采集的。 关于Muse EEG数据的精度,在…

避免90%以上IT故障,医院运维效率狂飙

一、故障发现到解决,仅用15分钟 一、问题描述 上午11点半左右,平台接到医院某软件PACS数据库离线和CPU使用率异常告警。 (告警信息) (告警详情) 二、查找问题的原因 cpu使用率时序图 从CPU使用率时序图中…

2022年全国研究生数学建模竞赛华为杯E题草原放牧策略研究求解全过程文档及程序

2022年全国研究生数学建模竞赛华为杯 E题 草原放牧策略研究 原题再现: 一、背景介绍   草原作为世界上分布最广的重要的陆地植被类型之一,分布面积广泛。中国的草原面积为3.55亿公顷,是世界草原总面积的6%~8%,居世界第二。此外…

Windows安装Neo4j

图数据库概述 图数据库是基于图论实现的一种NoSQL数据库,其数据存储结构和数据查询方式都是以图论(它以图为研究对象图论中的图是由若干给定的点及连接两点的线所构成的图形)为基础的, 图数据库主要用于存储更多的连接数据。 Neo…

1.Zigbee开发,环境搭建

一。环境搭建 1.开发环境 1.IAR开发环境搭建 2.TI官方必备软件安装 (安装此文件,类似Cubemx不同型号stm32的固件库)(这是协议栈) 3.仿真器及USB串口驱动安装 (就是使用串口烧录到板子上所需要的软件&#…

PyTorch实现注意力机制及使用方法汇总,附30篇attention论文

还记得鼎鼎大名的《Attention is All You Need》吗?不过我们今天要聊的重点不是transformer,而是注意力机制。 注意力机制最早应用于计算机视觉领域,后来也逐渐在NLP领域广泛应用,它克服了传统的神经网络的的一些局限&#xff0c…

【IBMMQ】搭建测试队列

一、安装IBMMQ 网上有教程,可以学习 我用的IBMMQ7.5,安装教程 二、创建测试队列 进入工作台: 右击队列管理器,新建队列管理器 写队列管理器名称 点击下一步 点击下一步 点击下一步 端口默认为1414,建议换一个 注…