链表的极致——带头双向循环链表

news2024/11/25 0:22:19


文章目录

  • 双向带头循环链表简介:
    • 双向:
    • 带头:
      • 特点:
      • 链表带头节点的好处:
    • 循环:
      • 特点:
      • 循环的好处:
  • 双向带头循环链表的接口函数实现
    • 准备工作:
  • 初始化链表(头结点)
  • 尾插
    • 参数设计
    • 图解
  • 打印链表
    • 图解
  • 头插
    • 图解
  • 尾删
    • 图解
  • 头删
    • 图解
  • 查找
  • 随机插入
    • 图解
  • 随机删除
    • 图解
  • 销毁链表
    • 图解
  • 全部代码
    • SList.h
    • SList.c

双向带头循环链表简介:

双向带头循环链表是链表结构最复杂,但使用最方便的链表。
在这里插入图片描述

[图中phead表示链表的头节点(哨兵);

d1,d2等表示链表存储的数据;

d1,d2等左侧的方框存储是指向该节点的上一个节点的指针(prev),右侧方框存储指向该节点的下一个的指针(next)]


双向:

双向带头循环链表的每一个节点的指针域都有俩个指针,一个指针(prev)指向该节点的前一个节点,一个指针(next)指向它的后一个节点。
解决了单链表只能向后访问,不能向前访问的问题


带头:

特点:

  1. 双向带头循环链表的头节点(哨兵)位于第一个存储数据的链表的前一个结点

  2. 双向带头循环链表有一个头节点(哨兵),该节点无论链表是否为空它都存在

  3. 头节点(哨兵)的数据域一般不存储数据或者存储链表的信息(有几个节点等)

  4. 双向带头循环链表的头节点(哨兵)指针域的也有两个指针,且指向前一个节点的指针(prev)指向链表的最后一个节点,指向下一个节点的指针(next)指向链表的第一个节点


链表带头节点的好处:

  1. 由于开始结点的位置被存放在头结点的指针域中,所以在链表的第一个节点上的操作就和在表的其它位置上操作一致,无须对链表的第一个节点进行进行特殊处理。 不会再改变链表phead指向的位置,也就不用再函数传参时有时传phead,有时传*phead,让链表的接口函数的参数类型也统一了。
  2. 无论链表是否为空,其头指针是指向头结点的非空指针,因此空表和非空表的处理也就统一了,不需要再分情况。

循环:

特点:

  1. 双向带头循环链表的最后一个节点的指向下一个节点的指针(next)指向头节点(哨兵)
  2. 头节点(哨兵)的指向上一个节点的指针(prev)指向链表的最后一个节点
  3. 当链表为空时,只有头节点(哨兵),此时头节点(哨兵)的prev指向它自己,它的next也指向它自己

循环的好处:

  1. 尾插时不用遍历找链表的最后一个节点(尾节点),因为指向双向带头循环链表的最后一个的节点的指针被存放在头节点(哨兵)的prev中。
  2. 在进行删除操作后,能保证链表不断开
  3. 从链表中任意结点出发都能遍历整个链表。
  4. 可以实现循环遍历,即在遍历到链表末尾后能够自动回到链表头部进行下一轮遍历。

双向带头循环链表的接口函数实现

准备工作:

创建一个头文件(SList.h)两个源文件(SList.c和test.c)

  • SList.h:用于包含库函数的头文件,链表节点结构体声明,接口函数的声明等【另外两个源文件要包含SList.h这个头文件,才能使用其中的声明】
  • SList.h:用于实现单链表的接口函数
  • test.c:存放main函数,用于链表的测试

在这里插入图片描述

上图包含了以下3个操作

1.库函数的头文件的包含:

stdio.h:输入/输出等函数
stdlib.h:动态内存申请
assert.h:报错函数assert
2.给链表节点的数据域的数据类型重命名
为什么要重命名呢?
这是为了以后如果改变了LTNoed结构体中数据存储的类型时,
不用到处改函数参数等地方的数据类型,只要改typedef后的int 为对应要改成的数据类型就可以。

3.链表节点结构体定义和重命名


初始化链表(头结点)

在这里插入图片描述
参数设计:
无需参数,将返回值赋值给一个指针,这个指针就成为链表的phead。


尾插

在这里插入图片描述


参数设计

LTNode*phead:
上面说了因为头指针(phead)不会改变,所以传一级指针phead即可

LTDaType x:
要插入的数据


图解

在这里插入图片描述
链表为空的时候也不需要像单链表那样特殊考虑,这也是双向带头循环链表的好处


打印链表

在这里插入图片描述


图解

在这里插入图片描述


头插

在这里插入图片描述


图解

在这里插入图片描述


尾删

在这里插入图片描述


图解

在这里插入图片描述


头删

在这里插入图片描述


图解

在这里插入图片描述


查找

在这里插入图片描述


随机插入

在这里插入图片描述
随机插入的pos要配合查找函数的返回值使用·


图解

在这里插入图片描述


随机删除

在这里插入图片描述


图解

在这里插入图片描述


销毁链表

在这里插入图片描述


图解

在这里插入图片描述


全部代码

SList.h

#define _CRT_SECURE_NO_WARNINGS

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

typedef int LTDateType;

typedef struct LTNode
{
	LTDateType data;
	struct LTNode* next;
	struct LTNode* prev;
}LTNode;

//初始化链表
LTNode* ListInit(void);
//打印链表
void ListPrint(LTNode* phead);
//尾插
void ListPushBack(LTNode* phead, LTDateType x);
//头插
void ListPushFront(LTNode* phead, LTDateType x);
//尾删
void ListPopBack(LTNode* phead);
//头删
void ListPopFront(LTNode* phead);
//查找x,找到了返回指向x的结构指针,找不到返回NULL
LTNode* ListFind(LTNode* phead, LTDateType x);
//在pos之前插入数据
void ListInsert(LTNode* phead, LTNode* pos, LTDateType x);
//删除pos指向的节点
void ListEase(LTNode* phead, LTNode* pos);
//销毁链表
void ListDestory(LTNode* phead);

SList.c

#define _CRT_SECURE_NO_WARNINGS
#include"List.h"

LTNode* ListInit()
{
	//哨兵位头节点
	LTNode* phead = (LTNode*)malloc(sizeof(LTNode));//为头结点申请空间

	phead->data = 0;//不存储链表数据(链表的长度等)时也可不初始化

	phead->next = phead;//链表为空时头结点的next指向自己
	phead->prev = phead;//链表为空时头结点的prev也指向自己

	return phead;//返回被一个节点(phead)接收
}

void ListPushBack(LTNode* phead, LTDateType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));//为新节点申请空间
	if (newnode == NULL)//如果为新节点申请空间失败
	{
		printf("malloc失败");
		exit(-1);//结束程序,-1表示程序非正常结束
	}
	LTNode* tail = phead->prev;//tail指向链表的最后一个节点

	tail->next = newnode;//让新节点成为原尾节点的下一个节点

	newnode->prev = tail;//让原尾节点成为新节点的上一个节点

	phead->prev = newnode;//更新头结点中存储的尾节点

	newnode->next = phead;//让新节点的下一个节点为头结点(phead)

	newnode->data = x;//存储数据
}

void ListPrint(LTNode* phead)
{
	LTNode* cur = phead->next;//将链表的第一个节点赋值给cur

	while (cur != phead)//因为双向带头循环链表没有指向NULL的指针了,而且链表的尾节点的next指向头结点(phead)
		                //所以遍历链表结束的条件为cur==phead
	{
		printf("%d  ", cur->data);//打印节点数据域的数据
		cur = cur->next;//让cur指向下一个节点
	}
}

//头插
void ListPushFront(LTNode* phead, LTDateType x)
{
	LTNode* cur = phead->next;//让cur指向链表的第一个节点
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));//为新节点申请空间

	if (newnode == NULL)//如果为新节点申请空间失败
	{
		printf("malloc失败");
		exit(-1);//结束程序,-1表示程序非正常结束
	}
	phead->next = newnode;//让头结点的下一个节点为新节点

	newnode->next = cur;//让新节点的下一个节点为原链表的第一个节点

	newnode->prev = phead;//让新节点的上一个节点为头结点

	newnode->data = x;

	cur->prev = newnode;//让原链表的第一个节点的上一个节点为新节点
}
//尾删
void ListPopBack(LTNode* phead)
{
	assert(phead->next != phead);//链表为空时不能再删除

	LTNode* tail = phead->prev;//让tail指向尾节点

	tail->prev->next = phead;//让尾节点(tail)的前一个节点的next指向头结点,

	phead->prev = tail->prev;//让删除之前的尾节点的前一个节点成为新的尾节点

	free(tail);//释放删除之前的尾节点
}
//头删
void ListPopFront(LTNode* phead)
{
	assert(phead->next != phead);//链表为空时不能再删除

	LTNode* cur = phead->next;//让cur指向链表的第一个节点

	phead->next = cur->next;//让头节点的下一个节点为原链表第一个节点的下一个节点(原链表的第二个节点)

	cur->next->prev = phead;//让原链表的第二个节点的前一个节点为头结点

	free(cur);//释放原链表第一个节点
}
//查找x,找到了返回指向x的结构指针,找不到返回NULL
LTNode* ListFind(LTNode* phead, LTDateType x)
{
	LTNode* cur = phead->next;//让cur指向链表的第一个节点

	while (cur != phead)//因为双向带头循环链表没有指向NULL的指针了,而且链表的尾节点的next指向头结点(phead)
		                //所以遍历链表结束的条件为cur==phead
	{
		if (cur->data == x)
		{
			return cur;//找到了就直接返回
		}
		else
		{
			cur = cur->next;//让cur指向下一个节点
		}
	}
	return NULL;//找不到就返回NULL
}

//在pos之前插入数据
void ListInsert(LTNode** phead, LTNode* pos, LTDateType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)//如果为新节点申请空间失败
	{
		printf("malloc失败");
		exit(-1);//结束程序,-1表示程序非正常结束
	}
	newnode->data = x;//存放数据

	newnode->next = pos;//让新节点的下一个节点为pos指向的节点

	newnode->prev = pos->prev;//让新节点的上一个节点为pos指向节点的前一个节点

	pos->prev->next = newnode;//pos指向节点的上一个节点的下一个节点为新节点

	pos->prev = newnode;//让新节点成为pos指向节点的上一个节点
}

//删除pos指向的节点
void ListEase(LTNode* phead, LTNode* pos)
{
	assert(pos != phead);//链表为空时再不能删除

	pos->prev->next = pos->next;//让pos指向节点的前一个节点的next指向pos指向节点的下一个节点

	pos->next->prev = pos->prev;//让pos指向节点的下一个节点的prev指向pos指向节点的上一个节点

	free(pos);//释放pos指向节点
}

//销毁链表
void ListDestory(LTNode* phead)
{
	LTNode* cur = phead->next;//让cur指向链表的第一个节点

	LTNode* tmp = phead->next;

	while (cur != phead)
	{
		tmp = cur->next;//tmp指向cur的下一个节点,防止cur被释放了,找不到下一个节点了
		free(cur);
		cur = tmp;//让cur指向下一个节点
	}
	phead->prev = phead;//链表的所有节点都被释放后,头节点的prev要指向自己,让链表下一次使用时不会出错

	phead->next = phead;//链表的所有节点都被释放后,头节点的next也要指向自己
}

以上就是所有内容了,如果对你有帮助的话,可以点个赞支持一下!

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

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

相关文章

C++:数据类型—布尔(12)

布尔类型代表就是真和假&#xff08;bool&#xff09; 真就是1&#xff08;true&#xff09; 假就是0&#xff08;false&#xff09; 也可以任务非0即为真 bool 直占用1个字节大小 语法&#xff1a;bool 变量名 (true | false&#xff09; 提示&#xff1a;bool在后期判断也是…

深度学习pytorch——经典卷积网络之ResNet(持续更新)

错误率前五的神经网络&#xff08;图-1&#xff09;&#xff1a; 图-1 可以很直观的看到&#xff0c;随着层数的增加Error也在逐渐降低&#xff0c;因此深度是非常重要的&#xff0c;但是学习更好的网络模型和堆叠层数一样简单吗&#xff1f;通过实现表明&#xff08;图-2&…

《自动机理论、语言和计算导论》阅读笔记:p49-p67

《自动机理论、语言和计算导论》学习第4天&#xff0c;p49-p67总结&#xff0c;总计19页。 一、技术总结 1.Deterministic Finite Automata(DFA) vs Nondeterministic Finite Automata(NFA) (1)DFA定义 (2)NFA定义 A “nonedeterministic” finite automata has the power t…

python之绘制曲线

以同一种型号的钻头&#xff0c;钻21种类型的板材&#xff0c;每种板材使用3根钻头&#xff0c;分别在钻第一个孔、2001孔、4001孔和6001孔前测量钻头外径&#xff0c;收集数据。 1、测试方法 采用激光钻径分选机测量微钻钻径以评估微钻外径磨损&#xff0c;测量从钻尖起始间…

C语言-文件

目录 1.什么是文件&#xff1f;1.1 程序文件1.2 数据文件 2.二进制文件和文本文件&#xff1f;3.文件的打开和关闭4.文件的顺序读写5.文件的随机读写5.1 fseek5.2 ftell5.3 rewind 6.文件读取结束的判定7.文件缓冲区 1.什么是文件&#xff1f; 磁盘上的文件就是文件 一般包含两…

使用pytorch构建带梯度惩罚的Wasserstein GAN(WGAN-GP)网络模型

本文为此系列的第三篇WGAN-GP&#xff0c;上一篇为DCGAN。文中仍然不会过多详细的讲解之前写过的&#xff0c;只会写WGAN-GP相对于之前版本的改进点&#xff0c;若有不懂的可以重点看第一篇比较详细。 原理 具有梯度惩罚的 Wasserstein GAN (WGAN-GP)可以解决 GAN 的一些稳定性…

caffe源码编译安装

一、前置准备 (1)vs2015 目前不要想着2019这些工具了,成功率太低了,就老老实实用vs2015吧 解决“VS2015安装包丢失或损坏“问题_vs2015跳过包会影响使用吗-CSDN博客 注意在安装vs2015过程中老是出现这个问题,其实就是缺少两个证书,安装完后就可以正常安装vs2015了,注意…

大数据面试专题 -- kafka

1、什么是消息队列&#xff1f; 是一个用于存放数据的组件&#xff0c;用于系统之间或者是模块之间的消息传递。 2、消息队列的应用场景&#xff1f; 主要是用于模块之间的解耦合、异步处理、日志处理、流量削峰 3、什么是kafka&#xff1f; kafka是一种基于订阅发布模式的…

AE——重构数字(Pytorch+mnist)

1、简介 AE&#xff08;自编码器&#xff09;由编码器和解码器组成&#xff0c;编码器将输入数据映射到潜在空间&#xff0c;解码器将潜在表示映射回原始输入空间。AE的训练目标通常是最小化重构误差&#xff0c;即尽可能地重构输入数据&#xff0c;使得解码器输出与原始输入尽…

什么是nginx正向代理和反向代理?

什么是代理&#xff1f; 代理(Proxy), 简单理解就是自己做不了的事情或实现不了的功能&#xff0c;委托别人去做。 什么是正向代理&#xff1f; 在nginx中&#xff0c;正向代理指委托者是客户端&#xff0c;即被代理的对象是客户端 在这幅图中&#xff0c;由于左边内网中…

如何解决kafka rebalance导致的暂时性不能消费数据问题

文章目录 背景思考答案排它故障转移共享 背景 之前在review同组其它业务的时候&#xff0c;发现竟然把kafka去掉了&#xff0c;问了下原因&#xff0c;有一个单独的服务&#xff0c;我们可以把它称为agent&#xff0c;就是这个服务是动态扩缩容的&#xff0c;会采集一些指标&a…

k8s的pod访问service的方式

背景 在k8s中容器访问某个service服务时有两种方式&#xff0c;一种是把每个要访问的service的ip注入到客户端pod的环境变量中&#xff0c;另一种是客户端pod先通过DNS服务器查找对应service的ip地址&#xff0c;然后在通过这个service ip地址访问对应的service服务 pod客户端…

HarmonyOS 应用开发之FA模型访问Stage模型DataShareExtensionAbility

概述 无论FA模型还是Stage模型&#xff0c;数据读写功能都包含客户端和服务端两部分。 FA模型中&#xff0c;客户端是由DataAbilityHelper提供对外接口&#xff0c;服务端是由DataAbility提供数据库的读写服务。 Stage模型中&#xff0c;客户端是由DataShareHelper提供对外接…

腾讯云2核2G服务器优惠价格,61元一年

腾讯云2核2G服务器多少钱一年&#xff1f;轻量服务器61元一年&#xff0c;CVM 2核2G S5服务器313.2元15个月&#xff0c;轻量2核2G3M带宽、40系统盘&#xff0c;云服务器CVM S5实例是2核2G、50G系统盘。腾讯云2核2G服务器优惠活动 txybk.com/go/txy 链接打开如下图&#xff1a;…

java数组与集合框架(三)--Map,Hashtable,HashMap,LinkedHashMap,TreeMap

Map集合&#xff1a; Map接口: 基于 键&#xff08;key&#xff09;/值&#xff08;value&#xff09;映射 Map接口概述 Map与Collection并列存在。用于保存具有映射关系的数据:key-value Map 中的key 和value 都可以是任何引用类型的数据Map 中的key 用Set来存放&#xff0…

X进制减法(蓝桥杯)

文章目录 X进制减法题目描述解题思路贪心算法模拟减法&#xff08;大数相减&#xff09; X进制减法 题目描述 进制规定了数字在数位上逢几进一。 X 进制是一种很神奇的进制&#xff0c;因为其每一数位的进制并不固定&#xff01;例如说某种 X 进制数&#xff0c;最低数位为二…

创建Qt Quick Projects

在创建Qt Quick项目之前&#xff0c;我们简单说一下Qml和Qt Quick的关系&#xff1a;它们的关系类似于C和STL标准库的关系&#xff0c;Qml类比C语言&#xff0c;提供了基本语言特性和类型&#xff1b;而Qt Quick则类比STL标准库&#xff0c;Qt Quick在QML的基础上加入了一系列界…

Https【Linux网络编程】

目录 一、为什么需要https 二、常见加密方法 1、对称加密 2、非对称加密 3、数据指纹 三、选择什么加密方案&#xff1f; 方案一&#xff1a;对称加密&#xff08;&#xff09; 方案二&#xff1a;双方使用非对称加密&#xff08;效率低&#xff09; 方案三&#xff1a…

深度学习十大算法之Diffusion扩散模型

1. 引言 扩散模型在近年来成为了热门话题&#xff0c;其火速蹿红主要归功于在图像生成领域的突破应用。尤其是一些从文本到图像的生成技术&#xff0c;它们成功地运用了扩散模型来创建令人惊叹的逼真图像。如果你听说过某个应用能够迅速且高质量地生成图像&#xff0c;那么很可…

【SpringBoot整合系列】SpirngBoot整合EasyExcel

目录 背景需求发展 EasyExcel官网介绍优势常用注解 SpringBoot整合EaxyExcel1.引入依赖2.实体类定义实体类代码示例注解解释 3.自定义转换器转换器代码示例涉及的枚举类型 4.Excel工具类5.简单导出接口SQL 6.简单导入接口SQL 7.复杂的导出&#xff08;合并行、合并列&#xff0…