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

文章目录

  • 写在前面
  • 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);//释放哨兵位的头节点
}

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

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

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

相关文章

基于Qt QList和QMap容器类示例

## QList<T> QList<T>容器是一个数组列表,特点如下: 1.大多数情况下可以用QList。像prepend()、append()和insert()这种操作,通常QList比QVector快的多。这是因为QList是基于index标签存储它的元素项在内存中(虽然内存不连续,这点与STL的list 是一样的),比…

23111904计算机程序设计-基于SpringbootfreemarkerMysql的宿舍寝室维修上报管理系统

文章目录 系统功能系统实现开发环境 编程技术交流、源码分享、模板分享、网课分享 企鹅&#x1f427;裙&#xff1a;776871563 系统功能 《基于SpringbootfreemarkerMysql实现的宿舍|寝室维修上报管理系统》该项目含有源码、文档等资料、配套开发软件、软件安装教程、项目发布…

软考小记-软件工程

模块的控制范围包括模块本身及其所有的从属模块。模块的作用范围是指模块一个判定的作用范围&#xff0c;凡是受这个判定影响的所有模块都属于这个判定的作用范围.&#xff0c;原则上一个模块的作用范围应该在其控制范围之内&#xff0c;若没有&#xff0c;则可以将判定所在模块…

Flutter最新稳定版3.16 新特性介绍

Flutter 3.16 默认采用 Material 3 主题&#xff0c;Android 平台预览 Impeller&#xff0c;DevTools 扩展等等 Flutter在每个季度通常都会有个稳定版本的发布。在2023 Q4的更新中为大家带来的是Flutter 3.16。这个版本将 Material 3 设为新的默认主题&#xff0c;并为 Android…

openGauss学习笔记-127 openGauss 数据库管理-设置账本数据库-修复账本数据库

文章目录 openGauss学习笔记-127 openGauss 数据库管理-设置账本数据库-修复账本数据库127.1 前提条件127.2 背景信息127.3 操作步骤 openGauss学习笔记-127 openGauss 数据库管理-设置账本数据库-修复账本数据库 127.1 前提条件 系统中需要有审计管理员或者具有审计管理员权…

JUnit 单元自动化

一、Junit 是什么&#xff1f; Junit 是 Java 中用于单元测试的框架。使用 Junit 能让我们快速高效的完成单元测试。 自动化测试&#xff1a;JUnit提供了自动化测试的能力&#xff0c;开发人员可以编写一次测试用例&#xff0c;然后通过简单的命令或集成到持续集成工具中进行…

Python----函数中的说明文档

说明文档&#xff1a;就是一行注释&#xff0c;在每次 定义一个函数后&#xff08;def XXX(): 的下一行&#xff09;&#xff0c;开发的人写一段注释文字&#xff0c;告诉别人这个函数是干嘛用的。 案例&#xff1a;定义函数的说明文档 ① 定义函数的说明文档 # 1、定义一个…

map与set的封装

目录 红黑树的结点 与 红黑树的迭代器 红黑树的实现&#xff1a; 迭代器&#xff1a; ​编辑 红黑树的查找&#xff1a; 红黑树的插入&#xff1a; ​编辑 检查红色结点&#xff1a;​编辑红黑树的左旋 ​编辑红黑树的右旋 ​编辑红黑树的双旋 Map的封装 ​编辑set的…

CnosDB有主复制演进历程

分布式存储系统的复杂性涉及数据容灾备份、一致性、高并发请求和大容量存储等问题。本文结合CnosDB在分布式环境下的演化历程&#xff0c;分享如何将分布式理论应用于实际生产&#xff0c;以及不同实现方式的优缺点和应用场景。 分布式系统架构模式 分布式存储系统下按照数据复…

2.5A、3MHz开关充电器解决方案

今天给大家介绍的是属于电源管理芯片中的开关型锂离子电池充电芯片&#xff0c;在前面介绍了一款锂离子电池充电池TP4054&#xff0c;相比于之前的那款芯片&#xff0c;这款芯片具有更强大的功能与应用。 基本概述 ETA6002是一款开关式锂离子电池充电器&#xff0c;具有动态电…

hypermesh常用快捷键

#hypermesh常用快捷键

UE 视差材质 学习笔记

视差材质节点&#xff1a; 第一个是高度图&#xff0c; Heightmap Channel就是高度图的灰色通道&#xff0c;在RGBA哪个上面&#xff0c;例如在R上就连接(1,0,0,0)&#xff0c;G上就连接&#xff08;0,1,0,0&#xff09;逐次类推 去看看对比效果&#xff1a; 这个是有视差效果…

OpenCV快速入门:窗口交互

文章目录 前言一、鼠标操作1.1 鼠标操作简介1.2 鼠标事件类型&#xff08;event类型&#xff09;1.3 鼠标事件标志&#xff08;flags&#xff09;1.4 代码示例1.4.1 获取鼠标坐标位置1.4.2 监听鼠标滚轮事件1.4.3 在图像中显示鼠标坐标 二、键盘操作2.1 代码示例2.2 waitKey的等…

5.Java中的注释及Javadoc文档

本文讲解 Java 中的注释以及 Javadoc 文档 ~ 文章目录 1. 注释1.1 引言1.1.1 何为注释&#xff1f;1.1.2 注释有何用&#xff1f;1.1.2.1 方便阅读1.1.2.2 调试程序 1.1.3 单行注释和多行注释 1.2 方法注释1.2.1 什么是方法注释&#xff1f;1.2.2 如何写方法注释&#xff1f;1.…

Java多线程(3)

Java多线程(3) 深入剖析Java线程的生命周期&#xff0c;探秘JVM的线程状态&#xff01; 线程的生命周期 Java 线程的生命周期主要包括五个阶段&#xff1a;新建、就绪、运行、阻塞和销毁。 **新建&#xff08;New&#xff09;&#xff1a;**线程对象通过 new 关键字创建&…

【C++】基础语法(中)

C基础语法&#xff08;中&#xff09; 文章目录 C基础语法&#xff08;中&#xff09;01数组一维数组数组初始化注意访问练习1练习2练习3普通做法&#xff1a;优化reverse函数练习4 多维数组清空数组memsetmemcpy 数组的部分由上到下&#xff0c;按规律 蛇形矩阵技巧 02 字符串…

23111903计算机程序设计-基于javaweb的旅游网站前台与后台旅景点

文章目录 系统实现开发环境 编程技术交流、源码分享、模板分享、网课分享 企鹅&#x1f427;裙&#xff1a;776871563 下面是系统运行起来后的部分截图&#xff1a; 系统实现 import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException;publi…

基于PHP+MySql的酒店信息管理系统的设计与实现

一、系统开发环境 运行环境&#xff1a;phpstudy或者wampserver&#xff0c; 开发工具&#xff1a;vscodephpstorm 数据库&#xff1a;mysql 二、酒店管理系统功能 1.前台功能&#xff1a; 首页客房推荐&#xff0c;周边特色介绍 酒店在线预订 订单查询&#xff0c;可以…

golang中的并发模型

并发模型 传统的编程语言&#xff08;如C、Java、Python等&#xff09;并非为并发而生的&#xff0c;因此它们面对并发的逻辑多是基于操作系统的线程。其并发的执行单元&#xff08;线程&#xff09;之间的通信利用的也是操作系统提供的线程或进程间通信的原语&#xff0c;比如…

基于Netty实现的简单聊天服务组件

目录 基于Netty实现的简单聊天服务组件效果展示技术选型&#xff1a;功能分析聊天服务基础设施配置&#xff08;基于Netty&#xff09;定义组件基础的配置&#xff08;ChatProperties&#xff09;定义聊天服务类&#xff08;ChatServer&#xff09;定义聊天服务配置初始化类&am…