数据结构——带头双向循环链表实现

news2024/11/27 18:39:45

目录

前言

一、结构介绍

二、增删查改的实现

1.在某一位置前插入数据

2.头插

3.尾插

4.删除某一位置的节点

5.头删

6.尾删

7.查找

8.打印

9.销毁

三、完整项目代码

1.头文件(List.h)

2.函数文件(List.c)

3.主函数测试文件(test.c)


前言

在学习数据结构过程中,我们经常要用到顺序表和链表。顺序表的优点是可以随机访问,缺点是增删效率低。链表恰好弥补了顺序表的缺点,但是不能随机访问,而且代码比较麻烦。因为在进行插入删除操作时,我们必须考虑链表为空和不为空两种情况。除此之外,想要访问当前节点的上一个节点,就必须把链表再遍历一遍,降低了运行速度。那么,有没有有一种可能,链表也能非常方便地插入、删除、找前后节点呢?这就是本文要为大家介绍的,带头双向循环链表。

一、结构介绍

我们从单链表开始探索:

单链表的节点由两部分组成,即存储的数据、指向下一个节点的指针。

 用代码定义如下:

typedef struct SListNode
{
	SLTDateType data;
	struct SListNode* next;
}SListNode;

这种结构是单向的,不能从后往前找,于是可以想到,在结构体中再加上一个指针,用来指向前一个节点,这样我们的链表就变成了双向链表。

 代码定义如下:

typedef struct SListNode
{
	SLTDateType data;
	struct SListNode* next;
    struct SListNode* prev;
}SListNode;

通过指针prev即可倒回去找前面的节点。这样能在一定程度上减少麻烦。

当然,这种情况下,进行插入操作时,还需要顾及链表是否为空的情况。两种情况操作不同,会增加代码量。因此,我们可以增加一个头节点head,不存储数据,只是占一个位置,我们称之为哨兵位。这样,即使链表为空,也有一个节点,可以进行访问next指针的操作。

以下是链表创建过程:

ListNode* BuyLTNode(LTDataType x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}

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

ListNode* ListCreate()
{
	ListNode* L = BuyLTNode(-1);
	L->next = NULL;
	L->prev = NULL;
	return L;
}

简单解释:创建一个新的节点,让其中存储-1(随便什么都行),prev和next指针置为空,这个节点是头节点,不存储实际数据,但是在插入时能简化操作。

头插方便了,但是如果要在尾部插入呢?你需要先创建一个指针,遍历链表,找到链表尾部,然后再插入,好麻烦那!别急,我们的带头双向链表继续升级,带头双向循环链表,就可以解决这个问题。

 以下是链表创建过程:

ListNode* BuyLTNode(LTDataType x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}

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

ListNode* ListCreate()
{
	ListNode* L = BuyLTNode(-1);
	L->next = L;
	L->prev = L;
	return L;
}

最初创建头节点时,头节点两个指针可以自己指向自己。

二、增删查改的实现

1.在某一位置前插入数据

void ListInsert(ListNode* pos, LTDataType x)
{
	assert(pos);
	ListNode* newnode = BuyLTNode(x);
	newnode->prev = pos->prev;
	pos->prev->next = newnode;
	newnode->next = pos;
	pos->prev = newnode;
}

在这里,不管是在哪里插入,都可以用这段代码,因为任何节点之间的结构都是一样的。可以配合另一个寻找节点的函数使用,先找到想要的节点,再在前面插入。

2.头插

void ListPushFront(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListInsert(pHead->next, x);
}

正因这种结构良好的兼容性,插入代码可以直接用来为头插函数服务。而在有头节点的情况下,只需通过头节点找next即可找到插入位置。

3.尾插

void ListPushBack(ListNode* pHead, LTDataType x)
{
    assert(pHead);
	ListInsert(pHead, x);
}

 插入代码也可以直接用来为尾插函数服务。而尾插是在链表最后插入,但是对于循环链表,链表的头即是尾,尾即是头,只需传头节点指针,头节点的前一个位置,就是链表尾。是不是巨方便?

4.删除某一位置的节点

void ListErase(ListNode* pos)
{
	assert(pos);
	ListNode* prev = pos->prev;
	ListNode* next = pos->next;
	prev->next = next;
	next->prev = prev;
	free(pos);
}

这里由于要删除节点,需要断开前后联系,可以先创建临时指针表示前后的节点,方便后面操作。

不管是哪里删除,都可以用这段代码。可以配合查找函数使用。

5.头删

void ListPopFront(ListNode* pHead)
{
	assert(pHead);
	ListErase(pHead->next);
}

有了插入函数,我们可以直接在头删函数中使用。注意头删可不是删除头节点,头节点只是占个位置,招你惹你了?删的是头节点的下一个位置,这才是第一个元素。

6.尾删

void ListPopBack(ListNode* pHead)
{
	assert(pHead);
	ListErase(pHead->prev);
}

插入函数在尾删也是可以直接用滴!想找尾?头节点的前一个就是!!!是不是巨方便?

7.查找

ListNode* ListFind(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListNode* cur = pHead->next;
	while (cur != pHead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

当然了,不管是是顺序表还是链表,要查找,他都得遍历。从头开始,找到那个存x的结点,返回它的地址就行。

8.打印

void ListPrint(ListNode* pHead)
{
	assert(pHead);
	ListNode* cur = pHead->next;
	while (cur != pHead)
	{
		printf("%d<==>", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

你别说,没这个函数其实也行,但是不好测试。这个函数主要是为了能在测试时把链表显示出来,让你知道,你成功辣!

9.销毁

void ListDestory(ListNode* pHead)
{
	assert(pHead);
	ListNode* cur = pHead->next;
	while (cur != pHead)
	{
		ListNode* prev = cur;
		cur = cur->next;
		free(prev);
	}
	free(pHead);
}

虽然退出程序操作系统会自动回收空间,但是如果这段代码用来做其他项目的接口,可能要运行很长时间,内存泄漏可是不好的。用完要销毁。每个节点都要free掉哦。

三、完整项目代码

1.头文件(List.h)

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
// 带头+双向+循环链表增删查改实现
typedef int LTDataType;
typedef struct ListNode
{
	LTDataType data;
	struct ListNode* next;
	struct ListNode* prev;
}ListNode;

// 创建返回链表的头结点.
ListNode* ListCreate();
// 双向链表销毁
void ListDestory(ListNode* pHead);
// 双向链表打印
void ListPrint(ListNode* pHead);
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x);
// 双向链表尾删
void ListPopBack(ListNode* pHead);
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x);
// 双向链表头删
void ListPopFront(ListNode* pHead);
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x);
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos);

2.函数文件(List.c)

#include "List.h"

ListNode* BuyLTNode(LTDataType x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}

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

ListNode* ListCreate()
{
	ListNode* L = BuyLTNode(-1);
	L->next = L;
	L->prev = L;
	return L;
}

void ListDestory(ListNode* pHead)
{
	assert(pHead);
	ListNode* cur = pHead->next;
	while (cur != pHead)
	{
		ListNode* prev = cur;
		cur = cur->next;
		free(prev);
	}
	free(pHead);
}

void ListPrint(ListNode* pHead)
{
	assert(pHead);
	ListNode* cur = pHead->next;
	while (cur != pHead)
	{
		printf("%d<==>", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

void ListPushBack(ListNode* pHead, LTDataType x)
{
	ListInsert(pHead, x);
}

void ListPopBack(ListNode* pHead)
{
	assert(pHead);
	ListErase(pHead->prev);
}

void ListPushFront(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListInsert(pHead->next, x);
}

void ListInsert(ListNode* pos, LTDataType x)
{
	assert(pos);
	ListNode* newnode = BuyLTNode(x);
	newnode->prev = pos->prev;
	pos->prev->next = newnode;
	newnode->next = pos;
	pos->prev = newnode;
}

void ListPopFront(ListNode* pHead)
{
	assert(pHead);
	ListErase(pHead->next);
}

ListNode* ListFind(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListNode* cur = pHead->next;
	while (cur != pHead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

void ListErase(ListNode* pos)
{
	assert(pos);
	ListNode* prev = pos->prev;
	ListNode* next = pos->next;
	prev->next = next;
	next->prev = prev;
	free(pos);
}

3.主函数测试文件(test.c)

#include"List.h"
int main()
{
	ListNode* pHead = NULL;
	pHead = ListCreate();
	printf("尾插1、2、3、4\n");
	ListPushBack(pHead, 1);
	ListPushBack(pHead, 2);
	ListPushBack(pHead, 3);
	ListPushBack(pHead, 4);
	ListPrint(pHead);
	printf("尾删一次\n");
	ListPopBack(pHead);
	ListPrint(pHead);
	printf("头插5、6、7、8\n");
	ListPushFront(pHead, 5);
	ListPushFront(pHead, 6);
	ListPushFront(pHead, 7);
	ListPushFront(pHead, 8);
	ListPrint(pHead);
	printf("头删一次\n");
	ListPopFront(pHead);
	ListPrint(pHead);
	printf("删除元素为1的节点\n");
	ListErase(ListFind(pHead, 1));
	ListPrint(pHead);
	printf("销毁链表\n");
	ListDestory(pHead);
	pHead = NULL;
	return 0;
}

记得最后将pHead制空,因为函数内部不能改变原指针,需要在外部制空,这样就永远找不到那份空间了。

这个测试代码可以很好地测试一下你写的函数是否可以运行起来(不包括函数的提醒报错功能,因为运行不起来不容易看结果)。那么,我们测试运行以下,结果如下:

 看来我们的代码非常成功。带头双向循环链表真是太好用辣!

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

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

相关文章

HQL语法

HQL基础语法 Hive中的语句叫做HQL语句,是一种类似SQL的语句,基本上和SQL相同但是某些地方也是有很大的区别. 数据库操作 创建数据库 1.创建一个数据库,数据库在HDFS上的默认存储路径是/hive/warehouse/*.db。 create database hive01; 避免要创建的数据库已经存在错误&…

【零基础学机器学习 3】机器学习类型简介:监督学习 - 无监督学习 - 强化学习

机器学习是人工智能的一种应用,从大量数据中学习并解决特定问题。它使用计算机算法,通过经验自动提高效率。 机器学习主要有三种类型:监督、无监督和强化学习。 监督学习 概述 监督学习是一种使用标记数据来训练机器学习模型的机器学习类型。在标记数据中,输出已经是已知…

javascript-基础知识点总结

目录 &#xff08;一&#xff09;基础语法 1、javaScript引入方式 2、变量与常量 3、数据类型 typeof操作符 4、运算符 5、输出函数 6、类型转化 7、转移字符 8、注释 &#xff08;二&#xff09;流程控制 1、选择结构 switch 2、循环结构 for &#xff08;三&…

简单网络管理协议 SNMP

文章目录 1 概述1.1 结构1.2 操作 2 SNMP2.1 报文格式2.2 五大报文类型 3 扩展3.1 网工软考真题 1 概述 #mermaid-svg-95KMV1m3prKJgwv1 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-95KMV1m3prKJgwv1 .error-ico…

【云原生】K8sYaml文件详细

K8sYaml文件详细 一、Kubernetes支持的文件格式1、yaml和json的主要区别2、yaml语言格式 二、YAML操作1、查看API资源版本标签2、编写资源配置清单3、创建资源对象4、查看创建的pod5、创建service服务对外提供访问并测试6、创建资源对象7、测试访问 三、Kubernetes中的Port四、…

chatgpt赋能Python-new_init_python

新版Python中的__new__和__init__方法 Python作为一种高级编程语言&#xff0c;具有简单易学、代码可读性高和编写效率高等特点&#xff0c;因此越来越受到程序员们的青睐。其中&#xff0c;__new__和__init__方法是Python中的两个非常重要的方法&#xff0c;它们在实例化一个…

chatgpt赋能Python-matplotlib绘制雷达图

Matplotlib 绘制雷达图 在数据分析和可视化领域&#xff0c;雷达图是一种常用的图表类型。Matplotlib 是一个用于绘制 2D 图形的 Python 库&#xff0c;也可以用于绘制雷达图。本文将介绍如何使用 Matplotlib 绘制雷达图&#xff0c;包括数据准备、绘图方式和样式设置。 数据…

改进YOLOv5 | 在 C3 模块中添加【SE】【CBAM】【 ECA 】【CA】注意力机制 | 附详细结构图

文章目录 1. SE 注意力模块1.1 原理1.2 C3_SE 代码2. CBAM 注意力模块2.1 原理2.2 C3_CBAM 代码3. CA 注意力模块3.1 原理3.2 C3_CA 代码4. ECA 注意力模块4.1 原理4.2 C3_ECA 代码5. 添加方式💡6. C3_Attention 结构图 🍀7. 相关推荐🍀

Ada语言学习(1)Basic Knowledge

文章目录 说在前头命名注释数字变量变量类型signed integersEnumerationsFloating Points 类型重用&#xff08;继承&#xff09;类型转换 运算符属性&#xff08;Attributes&#xff09;练习 说在前头 本系列教程将会通过提问的方式来完成整个学习过程&#xff0c;因为当你能…

瑞吉外卖 - 公共字段自动填充功能(10)

某马瑞吉外卖单体架构项目完整开发文档&#xff0c;基于 Spring Boot 2.7.11 JDK 11。预计 5 月 20 日前更新完成&#xff0c;有需要的胖友记得一键三连&#xff0c;关注主页 “瑞吉外卖” 专栏获取最新文章。 相关资料&#xff1a;https://pan.baidu.com/s/1rO1Vytcp67mcw-PD…

每日一练 | 网络工程师软考真题 Day3

1、以下图的两种编码方案分别是 。 A&#xff0e;①差分曼彻斯特编码&#xff0c;②双相码 B&#xff0e;①NRZ编码&#xff0c;②差分曼彻斯特编码 C&#xff0e;①NRZ-I编码&#xff0c;②曼彻斯特编码 D&#xff0e;①极性码&#xff0c;②双极性码 2、假设模拟…

windows下安装emsdk

先从github上下载emsdk工程&#xff1a; # Get the emsdk repo git clone https://github.com/emscripten-core/emsdk.git# Enter that directory cd emsdkgit pull 更新到最新代码 clone 工程可以在 git bash here 命令行下&#xff1a; 执行下面的指令用cmd命令行&#xf…

chatgpt赋能Python-numpy_加速

介绍 NumPy是Python编程语言的数值计算扩展库。它包括一个强大的N维数组对象和用于处理这些数组的函数。NumPy是一个基于Python的免费开源软件项目&#xff0c;由社区驱动。这个库提供了一种高效的存储和处理大型矩阵数据的方式&#xff0c;同时也提供了非常高效的处理大量数据…

chatgpt赋能Python-jinja_python

Jinja Python介绍及SEO优化 Jinja是一个流行的Python模板引擎&#xff0c;它允许开发人员使用简单的语法来生成动态内容。在本文中&#xff0c;我们将介绍Jinja的基础知识&#xff0c;并探讨如何将其应用于搜索引擎优化。 Jinja Python介绍 Jinja是Python语言的一种模板引擎…

Java面向对象程序设计实验报告(实验一 面向对象基础练习)

✨作者&#xff1a;命运之光 ✨ 专栏&#xff1a;Java面向对象程序设计实验报告 目录 ✨一、需求分析 ✨二、概要设计 ✨三、详细设计 ✨四、调试分析 ✨附录&#xff1a;源代码&#xff08;带注释&#xff09; 实验一 面向对象基础练习 实验环境&#xff1a;EclipseJDK …

MindFusion.JavaScript Pack 2023 crack

MindFusion.JavaScript Pack 2023 crack 径向树布局-添加了新的类&#xff0c;它将树级别排列在围绕根的同心圆中。 套索缩放工具-控件现在支持使用套索工具进行缩放的几种方法&#xff1a; 可以将行为属性设置为“缩放”以始终绘制缩放套索。 用户可以使用修改键和鼠标左键绘制…

如何用Nginx快速搭建个人下载站?

1.开篇 我们在下载Nginx的时候&#xff0c;发现其下载地址遵循一定的规则。 发现其下载地址为&#xff1a;https://nginx.org/download/nginx-1.24.0.tar.gz&#xff0c; 可以猜测出其下载的主页为&#xff1a;https://nginx.org/download 果然&#xff0c;该页面提供了各个版…

chatgpt赋能Python-num_python

NumPy简介&#xff1a;为什么它对数据科学如此重要&#xff1f; 什么是NumPy&#xff1f; NumPy是一种Python库&#xff0c;帮助程序员在Python中进行高度优化的科学计算。NumPy提供了许多独特的工具&#xff0c;以帮助数据科学家和工程师进行线性代数、离散傅立叶变换和其他…

三十七、微服务保护

1、初识Sentinel 1.1雪崩问题 微服务调用链路中的某个服务故障&#xff0c;引起整个链路中的所有微服务都不可用&#xff0c;这就是雪崩。 解决雪崩问题的常见方式有四种&#xff1a; 超时处理&#xff1a;设定超时时间&#xff0c;请求超过一定时间没有响应就返回错误信息&am…

DEJA_VU3D - Cesium功能集 之 109-三角形(标绘+编辑)

前言 编写这个专栏主要目的是对工作之中基于Cesium实现过的功能进行整合,有自己琢磨实现的,也有参考其他大神后整理实现的,初步算了算现在有差不多实现小140个左右的功能,后续也会不断的追加,所以暂时打算一周2-3更的样子来更新本专栏(每篇博文都会奉上完整demo的源代码…