C语言实现带头双向循环链表

news2024/9/24 21:26:17

文章目录

  • 写在前面
  • 1. 链表节点的定义
  • 2. 链表的初始化
  • 3. 插入数据
    • 3.1 头插
    • 3.2 尾插
    • 3.3 在指定位置的前面插入数据
  • 4 删除数据
    • 4.1 头删
    • 4.2 尾删
    • 4.3 删除指定位置的数据
  • 5 查找并修改数据
  • 5. 链表的销毁

写在前面

上面文章用C语言实现了单链表的增删查改,我们知道,单链表只能从头结点开始正向遍历,而在单链表中插入或删除节点时,需要修改前一个节点的指针,因此在单链表中插入或删除节点时需要遍历链表找到前一个节点,导致插入和删除操作的效率较低。为了能够高效率解决类似的问题,本片文章继续用C语言来实现另一种线性存储结构——带头双向循环链表。
我们从它的逻辑结构来更深层次的理解一下带头双向循环链表:
在这里插入图片描述

1. 链表节点的定义

链表的结点分为三部分:指针域、数据域、指针域
指针域:用于指向当前节点的直接前驱节点
数据域:链表要存储的数据所在的区域。
指针域:用于指向当前节点的直接后继节点。

链表节点的逻辑图:
在这里插入图片描述
链表节点的定义:

typedef int STDataType;
typedef struct ListNode
{
	struct ListNode* prev;//指针域, 指向上一个节点
	struct ListNode* next;//指针域, 指向下一个节点
	STDataType val;//数据域
}LTNode;

2. 链表的初始化

该链表在初始化的时候,只需要创建哨兵位的头节点即可,并将该节点的地址返回。该节点不存储有效数据,其prev 和 next指针指向自己。
在这里插入图片描述
由于后面的插入都需要创建新的节点,因此这里把创建节点封装成一个函数。

LTNode* BuyNode(LTDataType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror(" BuyNode()");
	}

	newnode->val = x;
	newnode->prev = NULL;
	newnode->next = NULL;
	return newnode;
}

链表的初始化代码如下:

LTNode* LTInit()
{
	//创建哨兵位的头节点
	LTNode* newnode = BuyNode(-1);
	
	//prev 和 next指针指向自己
	newnode->next = newnode;
	newnode->prev = newnode;
	return newnode;
}

3. 插入数据

向链表插入数据时,根据插入位置的不同可以分为以下三种情况:

  • 在头节点前插入一个元素,即头插。
  • 在链表中间位置插入元素。
  • 在最后一个节点后面插入一个元素,即尾插。

3.1 头插

头插数据步骤:

  1. 首先,创建一个新的节点,并用 val 初始化其数据域。
  2. 将新节点插入到链表的头部,更新指针。
    在这里插入图片描述
    代码如下:
void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);//检查参数有效性
	LTNode* newnode = BuyNode(x);//创建新节点

	LTNode* next = phead->next;

	//修改指针链接关系
	newnode->next = next;
	next->prev = newnode;

	newnode->prev = phead;
	phead->next = newnode;
}

3.2 尾插

尾插数据步骤:

  1. 首先,创建一个新的节点,并用 val 初始化其数据域。
  2. 将新节点插入到链表的尾部,更新指针。

在这里插入图片描述
代码如下:

void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);//检查参数有效性
	LTNode* newnode = BuyNode(x);//创建新节点

	LTNode* tail = phead->prev;

	//修改指针链接关系
	newnode->prev = tail;
	tail->next = newnode;

	newnode->next = phead;
	phead->prev = newnode;
	//LTInsert(phead, x);
}

3.3 在指定位置的前面插入数据

  1. 首先,创建一个新的节点,并用 val 初始化其数据域。
  2. 将新节点插入到链表的 pos 位置之前,更新指针。
    在这里插入图片描述

代码如下:

void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);//检查位置有效性
	LTNode* newnode = BuyNode(x);//创建新节点
	
	LTNode* posPrev = pos->prev;
	
	//修改指针链接关系
	pos->prev = newnode;
	newnode->next = pos;

	posPrev->next = newnode;
	newnode->prev = posPrev;
}

4 删除数据

4.1 头删

头删的步骤如下:

  1. 判断链表是否为空,不为空在进行删除。
    判断链表是否为空的代码如下:
bool LTEmpty(LTNode* phead)
{
	return phead->next == phead;
}
  1. 删除第一个节点,并更新指针。

在这里插入图片描述

代码如下:

void LTPopFront(LTNode* phead)
{
	assert(phead);//检查参数有效性
	assert(!LTEmpty(phead));//判断链表是否为空

	LTNode* pos = phead->next;
	LTNode* posNext = pos->next;

	free(pos);//删除第一个节点
	修改指针链接关系
	phead->next = posNext;
	posNext->prev = phead;
}

4.2 尾删

头删的步骤如下:

  1. 判断链表是否为空,不为空在进行删除。
  2. 删除最后一个节点,并更新指针。
    在这里插入图片描述
    代码如下:
void LTPopBack(LTNode* phead)
{
	assert(phead);//检查参数有效性
	assert(!LTEmpty(phead));//判断链表是否为空

	LTNode* tail = phead->prev;
	LTNode* tailPrev = tail->prev;

	free(tail);//删除最后一个节点
	修改指针链接关系
	phead->prev = tailPrev;
	tailPrev->next = phead;
}

4.3 删除指定位置的数据

注意:删除指定位置的数据,需要传递正确的节点的地址,否则删除的结果是不确定的。

在这里插入图片描述
代码如下:

void LTErase(LTNode* pos)
{
	LTNode* posPrev = pos->prev;
	LTNode* posNext = pos->next;

	free(pos);//删除指定位置的节点

	//修改指针链接关系
	posPrev->next = posNext;
	posNext->prev = posPrev;
}

5 查找并修改数据

遍历链表若找到目标节点,就返回目标节点的地址,否则返回空指针(NULL)。
该函数兼并修改的功能,因为该函数返回的是目标节点的地址。
代码如下:

LTNode* LTFind(LTNode* phead, LTDataType x)
{
	assert(phead);//检查参数有效性
	LTNode* cur = phead->next;
	//遍历链表
	while (cur != phead)
	{
		//找到返回,目标节点地址
		if (cur->val == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	//未找到,返回NLL
	return NULL;
}

5. 链表的销毁

  1. 依次释放链表的每个节点。
  2. 释放哨兵位的头节点。

注意:链表销毁以后,要在函数外面将头指针置空(NULL),以免造成野指针的问题。

代码如下:

void LTDestroy(LTNode* phead)
{
	assert(phead);//检查参数有效性
	LTNode* cur = phead->next;

	//依次释放链表的每个节点
	while (cur != phead)
	{
		LTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);//释放哨兵位的头节点
}

至此,本片文章就结束了,若本篇内容对您有所帮助,请三连点赞,关注,收藏支持下。
创作不易,白嫖不好,各位的支持和认可,就是我创作的最大动力,我们下篇文章见!
如果本篇博客有任何错误,请批评指教,不胜感激 !!!
在这里插入图片描述

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

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

相关文章

Python将已标注的两张图片进行上下拼接并修改、合并其对应的Labelme标注文件

Python将已标注的两张图片进行上下拼接并修改、合并其对应的Labelme标注文件 前言前提条件相关介绍实验环境上下拼接图片并修改、合并其对应的Labelme标注文件代码实现输出结果 前言 由于本人水平有限,难免出现错漏,敬请批评改正。更多精彩内容&#xff…

手写消息队列(基于RabbitMQ)

一、什么是消息队列? 提到消息队列是否唤醒了你脑海深处的记忆?回看前面的这篇文章:《Java 多线程系列Ⅳ(单例模式阻塞式队列定时器线程池)》,其中我们在介绍阻塞队列时说过,阻塞队列最大的用途…

【Linux】:体系结构与进程概念

朋友们、伙计们,我们又见面了,本期来给大家解读一下有关Linux体系结构和进程的知识点,如果看完之后对你有一定的启发,那么请留下你的三连,祝大家心想事成! C 语 言 专 栏:C语言:从入…

「Java开发指南」如何在Spring中使用JAX-WS注释器?

本文将指导您如何使用JAX-WS注释器从Spring服务生成JAX-WS Web服务,在本教程中,您将学习如何: 为Spring服务启用JAX-WS部署应用程序并测试服务 所有与Spring scaffolding相关的任务都需要MyEclipse Spring或Bling授权。 MyEclipse v2023.1…

『力扣刷题本』:二叉树的中序遍历

一、题目 给定一个二叉树的根节点 root ,返回 它的 中序 遍历 。 示例 1: 输入:root [1,null,2,3] 输出:[1,3,2]示例 2: 输入:root [] 输出:[]示例 3: 输入:root [1…

【NI-DAQmx入门】校准

1.设备定期校准的理由 随着时间的推移电子器件的特性会发生自然漂移,可能会导致测量结果的不准确性。防止出现良品和差品筛选出错的情况满足行业国际标准降低设备出现故障的风险使测量结果更具备参考性 2.查找NI设备的校准间隔。 定期校准会使DAQ设备的精度保持在…

Linux远程工具专家推荐(二)

8. Apache Guacamole Apache Guacamole 是一款免费开源的无客户端远程桌面网关,支持 VNC、RDP 和 SSH 等标准协议。无需插件或客户端软件;只需使用 HTML5 Web 应用程序(例如 Web 浏览器)即可。 这意味着您的计算机的使用不受任何一…

线性表--链表-1

文章目录 主要内容一.链表练习题1.设计一个递归算法,删除不带头结点的单链表 L 中所有值为 X 的结点代码如下(示例): 2.设 L为带头结点的单链表,编写算法实现从尾到头反向输出每个结点的值代码如下(示例): …

Jave 定时任务:使用Timer类执行定时任务为何会发生任务阻塞?如何解决?

IDE:IntelliJ IDEA 2022.2.3 x64 操作系统:win10 x64 位 家庭版 JDK: 1.8 文章目录 一、Timer类是什么?二、Timer类主要由哪些部分组成?1.TaskQueue2. TimerThread 三、示例代码分析四、自定义TimerTask为什么会发生任务相互阻塞的…

度加创作工具 演示

度加创作工具 功能图功能测试文比润色测试经验分享测试测试输出测试输出工具地址功能图 功能测试 文比润色测试 经验分享测试 测试输出 在人工智能领域,我们一直在追求一个终极目标:让机器能够像人类一样,能够理解、学习和解决各种复杂问题。而要实现这个目标,我们需要将…

设计模式常见面试题

简单梳理下二十三种设计模式,在使用设计模式的时候,不仅要对其分类了然于胸,还要了解每个设计模式的应用场景、设计与实现,以及其优缺点。同时,还要能区分功能相近的设计模式,避免出现误用的情况。 什么是…

麒麟系统安装找不到安装源!!!!设置基础软件仓库时出错

记录--华为RH2288 V3服务器安装麒麟系统遇到的问题 1.遇到的问题--“设置基础软件仓库时出错”报错导致无法继续安装 没办法下一步 先说结论:系统bug 该问题在CentOS、Rocky Linux最新版中均存在 解决: (一)、如果是外网直接配…

【机器学习基础】决策树(Decision Tree)

🚀个人主页:为梦而生~ 关注我一起学习吧! 💡专栏:机器学习 欢迎订阅!后面的内容会越来越有意思~ ⭐特别提醒:针对机器学习,特别开始专栏:机器学习python实战 欢迎订阅&am…

metinfo 5.0.4 文件包含漏洞复现

metinfo 5.0.4 文件包含漏洞 漏洞环境 metinfo cms 版本 5.0.4 代码审计 在metinfo下的about/index.php代码中发现动态调用 上面没有赋值但是是有具体值的说明在上一个文件包含赋值了 查看这个文件的源代码 可以看到这里做了初始化但是是在fmodule不等于7的时候那假设等…

深入解析具名导入es6规范中的具名导入是在做解构吗

先说答案,不是 尽管es6的具名导入和语法非常相似 es6赋值解构 const obj {a: 1,f() {this.a}}const { a, f } objes6具名导入 //导出文件代码export let a 1export function f() {a}export default {a,f}//导入文件代码import { a, f } from ./tsVolution可以看出…

【Go入门】Web工作方式

【Go入门】 Web工作方式 我们平时浏览网页的时候,会打开浏览器,输入网址后按下回车键,然后就会显示出你想要浏览的内容。在这个看似简单的用户行为背后,到底隐藏了些什么呢? 对于普通的上网过程,系统其实是这样做的&…

gd32关于IO引脚配置的一些问题

一、gd32f103的PA15问题 1、 #define GPIO_SWJ_NONJTRST_REMAP ((uint32_t)0x00300100U) /*!< full SWJ(JTAG-DP SW-DP),but without NJTRST */ #define GPIO_SWJ_SWDPENABLE_REMAP ((uint32_t)0x00300200U) /*!< JTAG-DP disabled and SW-DP enab…

【C++】——阶段性测验(帮助巩固C++前半部分知识)

&#x1f383;个人专栏&#xff1a; &#x1f42c; 算法设计与分析&#xff1a;算法设计与分析_IT闫的博客-CSDN博客 &#x1f433;Java基础&#xff1a;Java基础_IT闫的博客-CSDN博客 &#x1f40b;c语言&#xff1a;c语言_IT闫的博客-CSDN博客 &#x1f41f;MySQL&#xff1a…

安装2023最新版PyCharm来开发Python应用程序

安装2023最新版PyCharm来开发Python应用程序 Install the Latest JetBrains PyCharm Community to Develop Python Applications Python 3.12.0最新版已经由其官网python.org发布&#xff0c;这也是2023年底的最新的版本。 0. PyCharm与Python 自从1991年2月20日&#xff0…

Python---练习:封装一个函数,用于生成指定长度的验证码

练习涉及相关链接&#xff1a;Python---练习&#xff1a;编写一段Python代码&#xff0c;生成一个随机的4位验证码-CSDN博客 Python----函数中的说明文档-CSDN博客Python---return返回值-CSDN博客 代码&#xff1a; # 定义一个generate_code()函数 def generate_code(num): …