【数据结构】带头双向循环链表的实现

🌇个人主页:平凡的小苏

📚学习格言:别人可以拷贝我的模式,但不能拷贝我不断往前的激情

🛸C语言专栏:https://blog.csdn.net/vhhhbb/category_12174730.html

🚀数据结构专栏:https://blog.csdn.net/vhhhbb/category_12211053.html

        家人们更新不易,你们的👍点赞👍和⭐关注⭐真的对我真重要,各位路过的友友麻烦多多点赞关注,欢迎你们的私信提问,感谢你们的转发!

        关注我,关注我,关注我,你们将会看到更多的优质内容!!

 

       

1、带头循环双向链表

        我们在单链表中,有了next指针,这使得我们要查找下一节点的时间复杂度为O(1)。可是如果我们要查找的是上一节点的话,那最坏的时间复杂度就是O(n)了,因为我们每次都要从头开始遍历查找。

        为了克服单向性这一缺点,我们的老科学家们,设计出了双向链表。双向链表是在单链表的每个结点中,再设置一个指向其前驱结点的指针域。所以再双向链表中的结点都有两个指针域,一个指向直接后继,另一个指向直接前驱。

        既然单链表可以有循环链表,那么双向链表也可以有循环双向链表。(如下图所示)

2、双向循环链表函数接口的实现

2.1、双向循环链表的结构

typedef int LTDataType;
typedef struct ListNode
{
	LTDataType _data;//数据
	struct ListNode* _next;//后继指针
	struct ListNode* _prev;//前驱指针
}ListNode;

2.2、初始化双向循环链表

ListNode* ListCreate()
{
	ListNode* phead = (ListNode*)malloc(sizeof(ListNode));
	if (phead == NULL)
	{
		perror("malloc Fail:");
		exit(-1);
	}
	phead->_next = phead;
	phead->_data = -1;
	phead->_prev = phead;
	return phead;
}

由于是带头循环链表,我们需要malloc一个头节点出来,当链表是空的时候,前驱指针和后继指针都指向头结点。

2.3、双向循环链表的插入 

// 创建返回链表的头结点.
ListNode* BuyListNode(LTDataType x)
{
	ListNode* newhead = (ListNode*)malloc(sizeof(ListNode));
	if (newhead == NULL)
	{
		perror("malloc fail:");
		exit(-1);
	}
	newhead->_data = x;
	newhead->_next = NULL;
	newhead->_prev = NULL;
	return newhead;
}
//双链表插入
void ListInsert(ListNode* pos, LTDataType x)
{
	assert(pos);
	ListNode* newhead = BuyListNode(x);//该函数是创建新节点的函数
	ListNode* Prev = pos->_prev;
	Prev->_next = newhead;
	newhead->_prev = Prev;
	newhead->_next = pos;
	pos->_prev = newhead;
}

注:由于我们是在pos的前面插入一个结点,那么我们就应该保存上一个结点。

插入算法的具体操作步骤:

        1.Prev->_next = newhead;

        2.newhead->_prev = Prev;

        3.newhead->_next = pos;

        4.pos->_prev = newhead;

2.4、双向循环链表的删除操作

// 双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{
	assert(pos);//删除前pos不能为空
	assert(!ListEmpty(pos));//链表不为空才能删
	ListNode* ne = pos->_next;//保存pos位置的后一个结点
	pos->_prev->_next = ne;//删除结点的具体操作
	ne->_prev = pos->_prev;
	free(pos);//释放
}

2.5、双向循环链表的判空

bool ListEmpty(ListNode* pHead)
{
	assert(pHead);
	return pHead->_next == pHead;如果头结点的下一个结点也等于头结点的话那么链表为空
}

2.6、双向循环链表的打印

// 双向链表打印
void ListPrint(ListNode* pHead)
{
	assert(pHead);
	ListNode* cur = pHead->_next;
	while (cur != pHead)
	{
		printf("%d ", cur->_data);
		cur = cur->_next;
	}
	printf("\n");
}

2.7、双向循环链表的销毁

// 双向链表销毁
void ListDestory(ListNode* pHead)
{
	assert(pHead);
	ListNode* cur = pHead->_next;
	while (cur != pHead)//链表要遍历释放
	{
		ListNode* ne = cur->_next;
		free(cur);
		cur = ne;
	}
	free(pHead);
	pHead = NULL;
}

3、源代码

由于头插、头删、尾插、尾删可以用双向循环链表的插入和删除操作复用,这里直接放置源代码。

3.1、DList.c 

#include"DSList.h"
// 创建返回链表的头结点.
ListNode* BuyListNode(LTDataType x)
{
	ListNode* newhead = (ListNode*)malloc(sizeof(ListNode));
	if (newhead == NULL)
	{
		perror("malloc fail:");
		exit(-1);
	}
	newhead->_data = x;
	newhead->_next = NULL;
	newhead->_prev = NULL;
	return newhead;
}
ListNode* ListCreate()
{
	ListNode* phead = (ListNode*)malloc(sizeof(ListNode));
	if (phead == NULL)
	{
		perror("malloc Fail:");
		exit(-1);
	}
	phead->_next = phead;
	phead->_data = -1;
	phead->_prev = phead;
	return phead;
}
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListNode* newhead = BuyListNode(x);
	ListNode* tail = pHead->_prev;
	newhead->_prev = tail;
	tail->_next = newhead;
	newhead->_next = pHead;
	pHead->_prev = newhead;
	//ListInsert(pHead, x);
}
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListNode* newhead = BuyListNode(x);
	ListNode* first = pHead->_next;
	newhead->_prev = pHead;
	pHead->_next = newhead;
	newhead->_next = first;
	first->_prev = newhead;
	//ListInsert(pHead->_next, x);
}
// 双向链表在pos的前面进行插入

//判空
bool ListEmpty(ListNode* pHead)
{
	assert(pHead);
	return pHead->_next == pHead;
}
// 双向链表尾删
void ListPopBack(ListNode* pHead)
{
	assert(pHead);
	assert(!ListEmpty(pHead));
	ListNode* tail = pHead->_prev;
	ListNode* prevtail = tail->_prev;
	prevtail->_next = pHead;
	pHead->_prev = prevtail;
	free(tail);
	//ListErase(pHead->_prev);
}
// 双向链表头删
void ListPopFront(ListNode* pHead)
{
	assert(pHead);
	assert(!ListEmpty(pHead));
	ListNode* first = pHead->_next;
	pHead->_next = first->_next;
	first->_next->_prev = pHead;
	free(first);
	//ListErase(pHead->_next);
}
//双链表插入
void ListInsert(ListNode* pos, LTDataType x)
{
	assert(pos);
	ListNode* newhead = BuyListNode(x);
	ListNode* Prev = pos->_prev;
	Prev->_next = newhead;
	newhead->_prev = Prev;
	newhead->_next = pos;
	pos->_prev = newhead;
}
// 双向链表查找
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;
}
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{
	assert(pos);
	assert(!ListEmpty(pos));
	ListNode* ne = pos->_next;
	pos->_prev->_next = ne;
	ne->_prev = pos->_prev;
	free(pos);
}
// 双向链表打印
void ListPrint(ListNode* pHead)
{
	assert(pHead);
	ListNode* cur = pHead->_next;
	while (cur != pHead)
	{
		printf("%d ", cur->_data);
		cur = cur->_next;
	}
	printf("\n");
}
// 双向链表销毁
void ListDestory(ListNode* pHead)
{
	assert(pHead);
	ListNode* cur = pHead->_next;
	while (cur != pHead)
	{
		ListNode* ne = cur->_next;
		free(cur);
		cur = ne;
	}
	free(pHead);
	pHead = NULL;
}

3.2、DList.h

#include<stdio.h>
#include<assert.h>
#include<stdbool.h>
#include<stdlib.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);
//判空
bool ListEmpty(ListNode* pHead);

好了!!!小编的分享到这里就结束了,有什么不足的地方请大佬多多指教!

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

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

相关文章

【JavaEE】前后端分离实现博客系统(后端实现)

写在前面 Hello&#xff0c;在上一篇中&#xff0c;我们已经实现了对于博客系统的页面构建任务。本次主要解决的问题就是针对这四个界面&#xff0c;实现后端的 servlet 程序&#xff0c;规范前后端交互的接口&#xff0c;编写客户端和服务端代码&#xff0c;处理请求并反馈。博…

响应式编程详解,带你熟悉Reactor响应式编程

文章目录一、什么是响应式编程1、Java的流和响应式流2、Java中响应式的使用3、Reactor中响应式流的基本接口4、Reactor中响应式接口的基本使用二、初始Reactor1、Flux和Mono的基本介绍2、引入Reactor依赖3、响应式类型的创建4、响应式类型的组合&#xff08;1&#xff09;使用m…

【C语言蓝桥杯每日一题】——数字三角形

【C语言蓝桥杯每日一题】—— 数字三角形&#x1f60e;前言&#x1f64c;数字三角形&#x1f64c;总结撒花&#x1f49e;&#x1f60e;博客昵称&#xff1a;博客小梦 &#x1f60a;最喜欢的座右铭&#xff1a;全神贯注的上吧&#xff01;&#xff01;&#xff01; &#x1f60a…

QEMU启动ARM32 Linux内核

目录前言前置知识ARM Versatile Express开发板简介ARM处理器家族简介安装qemu-system-arm安装交叉编译工具交叉编译ARM32 Linux内核交叉编译ARM32 Busybox使用busybox制作initramfs使用QEMU启动ARM32 Linux内核模拟vexpress-a9开发板模拟vexpress-a15开发板参考前言 本文介绍采…

编译原理

文章目录绪论第1章 绪论1.什么是编译2.编译系统的结构3.词法分析第2章 语言及其文法字母表 ∑\sum∑概念终结符非终结符产生式文法Chomsky文法分类体系0型文法 &#xff08;Type-0 Grammar&#xff09;1型文法&#xff08;Type-1 Grammar&#xff09;2型文法&#xff08;Type-2…

JAVA开发与JAVA(一文学会使用ElasticSearch)

在web网站的架设中特别是数据量大的网站或者APP小程序需要搜索或者全文检索的场景&#xff0c;几乎都需要借助ElasticSearch来作为全文检索引擎&#xff0c;以提高网站的搜索效率和性能。 这一节&#xff0c;我们通过一篇文章介绍&#xff0c;使大家通过一文就学会使用Elastic…

python 函数:定义、调用、参数、返回值、嵌套、变量的作用域(局部变量、全局变量)、global、匿名函数lambda

函数可以将我们的程序分解成最小的模块&#xff0c;避免重复使用。函数内部的代码&#xff0c;只有被调用的时候才会执行。 函数的定义&#xff08;def就是define&#xff09;&#xff1a; 格式&#xff1a;def 函数名(): 函数封装的代码 函数的调用&#xff1a; 格式&…

大学生考研的意义?

当我拿起笔头&#xff0c;准备写这个话题时&#xff0c;心里是非常难受的&#xff0c;因为看到太多的学生在最好的年华&#xff0c;在自由的大学本应该开拓知识&#xff0c;提升认知&#xff0c;动手实践&#xff0c;不断尝试和试错&#xff0c;不断历练自己跳出学生思维圈&…

15000 字的 SQL 语句大全 第一部分

一、基础 1、说明&#xff1a;创建数据库CREATE DATABASE database-name 2、说明&#xff1a;删除数据库drop database dbname 3、说明&#xff1a;备份sql server--- 创建 备份数据的 device USE master EXEC sp_addumpdevice disk, testBack, c:\mssql7backup\MyNwind_1.dat …

数据结构--二叉树

目录1.树概念及结构1.1数的概念1.2数的表示2.二叉树概念及结构2.1二叉树的概念2.2数据结构中的二叉树2.3特殊的二叉树2.4二叉树的存储结构2.4.1顺序存储2.4.2链式存储2.5二叉树的性质3.堆的概念及结构3.1堆的实现3.1.1堆的创建3.1.2堆的插入3.1.3堆顶的删除3.1.4堆的代码实现3.…

蓝桥杯刷题冲刺 | 倒计时26天

作者&#xff1a;指针不指南吗 专栏&#xff1a;蓝桥杯倒计时冲刺 &#x1f43e;马上就要蓝桥杯了&#xff0c;最后的这几天尤为重要&#xff0c;不可懈怠哦&#x1f43e; 文章目录1.路径2.特别数的和3.MP3储存4.求和1.路径 题目 链接&#xff1a; 路径 - 蓝桥云课 (lanqiao.cn…

算法学习之二分查找

&#x1f383;个人主页&#x1f383;&#xff1a;勇敢的小牛儿 &#x1f9e8;推荐专栏&#x1f9e8;&#xff1a;C语言知识点 ✨座右铭✨&#xff1a;敢于尝试才有机会 ⚠️今日鸡汤⚠️&#xff1a;Is the true wisdom fortitude ambition. -- Napoleon 真正的才智是刚毅的志向…

【云原生·Docker】常用命令

目录 &#x1f341;1、管理命令 &#x1f341;2、帮助命令 &#x1f341;3、镜像命令 &#x1f341;4、容器命令 &#x1f342;4.1.查看容器 &#x1f342;4.2.创建容器 &#x1f342;4.3.删除容器 &#x1f342;4.4.拷贝文件 &#x1f342;4.5.查看容器IP &#x1f341;5、部署…

LSTM从入门到精通(形象的图解,详细的代码和注释,完美的数学推导过程)

先附上这篇文章的一个思维导图什么是RNN按照八股文来说&#xff1a;RNN实际上就是一个带有记忆的时间序列的预测模型RNN的细胞结构图如下&#xff1a;softmax激活函数只是我举的一个例子&#xff0c;实际上得到y<t>也可以通过其他的激活函数得到其中a<t-1>代表t-1时…

C语言/动态通讯录

本文使用了malloc、realloc、calloc等和内存开辟有关的函数。 文章目录 前言 二、头文件 三、主界面 四、通讯录功能函数 1.全代码 2.增加联系人 3.删除联系人 4.查找联系人 5.修改联系人 6.展示联系人 7.清空联系人 8.退出通讯录 总结 前言 为了使用通讯录时&#xff0c;可以…

Opencv项目实战:22 物体颜色识别并框选

目录 0、项目介绍 1、效果展示 2、项目搭建 3、项目代码展示与部分讲解 Color_trackbar.py bgr_detector.py test.py 4、项目资源 5、项目总结 0、项目介绍 本次项目要完成的是对物体颜色的识别并框选&#xff0c;有如下功能&#xff1a; &#xff08;1&#xff09;…

线程池的使用:如何写出高效的多线程程序?

目录1.线程池的使用2.编写高效的多线程程序Java提供了Executor框架来支持线程池的实现&#xff0c;通过Executor框架&#xff0c;可以快速地创建和管理线程池&#xff0c;从而更加方便地编写多线程程序。 1.线程池的使用 在使用线程池时&#xff0c;需要注意以下几点&#xff…

GDAL python教程基础篇(7)OGR空间计算

1.空间计算 地理数据处理&#xff08;geoprocessing&#xff09;计算函数&#xff1a; 多边形&#xff08;Polygon&#xff09;&#xff1a; 1、交&#xff1a;poly3.Intersection(poly2) 2、并&#xff1a;poly3.Union(poly2) 3、差&#xff1a;poly3.Difference(poly2) 4、补…

python打包成apk界面设计,python打包成安装文件

大家好&#xff0c;给大家分享一下如何将python程序打包成apk文件&#xff0c;很多人还不知道这一点。下面详细解释一下。现在让我们来看看&#xff01; 1、如何用python制作十分秒加减的apk 如何用python制作十分秒加减的apk&#xff1f;用法:. apk包放入apk文件目录,然后输入…

Linux基础命令大全(下)

♥️作者&#xff1a;小刘在C站 ♥️个人主页&#xff1a;小刘主页 ♥️每天分享云计算网络运维课堂笔记&#xff0c;努力不一定有收获&#xff0c;但一定会有收获加油&#xff01;一起努力&#xff0c;共赴美好人生&#xff01; ♥️夕阳下&#xff0c;是最美的绽放&#xff0…
最新文章