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

news2024/11/25 9:40:23

前言 

        带头双向循环链表是一种重要的数据结构,它的结构是很完美的,它弥补了单链表的许多不足,让我们一起来了解一下它是如何实现的吧!

1.节点的结构

        它的节点中存储着数据和两个指针,一个指针_prev用来记录前一个节点的地址,另一个指针_next 用来记录后一个节点的地址。

typedef int LTDataType;
typedef struct ListNode
{
	struct ListNode* _next;
	struct ListNode* _prev;
	LTDataType _data;
}ListNode;

2.尾部的插入和删除的实现

        由于这是带头循环的双向链表,所以尾插只需要在它的头结点的_prev 处进行插入,然后重新链接就可以了。如图: 

        如果只有一个头结点,插入也是一样的。

void ListPushBack(ListNode* phead, LTDataType data)//尾插
{
	assert(phead);
	ListNode* newNode = BuyListNode(data);//申请新节点

	ListNode* tail = phead->_prev;
	//找尾结点
	//链接新节点和尾结点
	tail->_next = newNode;
	newNode->_prev = tail;
	//与头结点进行链接
	phead->_prev = newNode;
	newNode->_next = phead;
}

         尾部的删除,首先需要找到链表的尾和尾的前一个节点,删除尾结点之后,将前一个节点进与头结点进行链接,如图:

void ListPopBack(ListNode* phead)//尾删除
{
	//确保指针不为空
	assert(phead);
	assert(phead->_next != phead);//保留头结点
	//找尾
	ListNode* tail = phead->_prev;
	ListNode* newTail = tail->_prev;//找到新的尾结点
	//删除旧的尾结点
	free(tail);
	//重新链接头尾节点
	newTail->_next = phead;
	phead->_prev = newTail;
}

3.头部插入和删除的实现

        头部的插入,删除和尾部的插入,删除类似,需要注意的是删除的不是 头结点,是存储有效数据的第一个节点,插入数据也是在第一个有效数据节点和头结点之间插入。如图:

 

void ListPushFront(ListNode* phead, LTDataType data)//头插
{
	//确保指针不为空
	assert(phead);
	//申请新的节点
	ListNode* newNode = BuyListNode(data);
	//进行链接
	ListNode* realHead = phead->_next;
	realHead->_prev = newNode;
	newNode->_next = realHead;
	phead->_next = newNode;
	newNode->_prev = phead;
}
void ListPopFront(ListNode* phead)//头删插
{
	//指针存在
	assert(phead);
	//并且确保不能删除头结点
	assert(phead->_next != phead);
	//找到真正的头
	ListNode* realHead = phead->_next;
	ListNode* realHeadNext = realHead->_next;
	//删除头节点
	free(realHead);
	//重新链接
	phead->_next = realHeadNext;
	realHeadNext->_prev = phead;
}

4.任意位置的插入和删除 

        在任意位置进行插入和删除,需要知道节点的指针,插入或者删除节点之后需要 更新节点的链接关系。如图:

 

void ListInsert(ListNode* pos, LTDataType data)//pos位置之前的插入
{
	assert(pos);//确保指针有效
	ListNode* newNode = BuyListNode(data);//申请节点
	//进行链接
	ListNode* prev = pos->_prev;
	ListNode* next = pos;

	prev->_next = newNode;
	newNode->_prev = prev;

	newNode->_next = next;
	next->_prev = newNode;
}
void ListErase(ListNode* pos)//pos位置的删除
{
	//确保指针有效
	assert(pos);
	ListNode* next = pos->_next;
	ListNode* prev = pos->_prev;
	//删除pos所指向的节点
	free(pos);
	//进行重新链接
	prev->_next = next;
	next->_prev = prev;
}

         对任意位置的插入和删除进行测试时,可以通过复用接口来实现,头插尾插,头删尾删都可以调用这两个接口来实现,如下:

void ListPushBack(ListNode* phead, LTDataType data)//尾插
{
	ListInsert(phead, data);
}
void ListPopBack(ListNode* phead)//尾删除
{
	ListErase(phead->_prev);
}
void ListPushFront(ListNode* phead, LTDataType data)//头插
{
	ListInsert(phead->_next,data);
}
void ListPopFront(ListNode* phead)//头删插
{
	ListErase(phead->_next);
}

5.链表的初始化和删除

        带头的双向循环链表初始化的时候就需要申请内存给头节点。 

ListNode* BuyListNode(LTDataType data)//申请节点
{
	ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));
	if (newNode == NULL)
	{
		printf("申请空间失败\n");
		exit(-1);
	}
	newNode->_data = data;
	return newNode;
}
void ListInit(ListNode** pphead)//初始化链表
{
	*pphead = BuyListNode(0);
	//申请头结点
	//并且初始化
	(*pphead)->_next = *pphead;
	(*pphead)->_prev = *pphead;
}
//清理链表
void ListClear(ListNode* phead)
{
	assert(phead);//确保链表不为空
	assert(phead->_next != phead);//除了确保不清理头结点
	ListNode* cur = phead->_next;

	while (cur != phead)
	{
		ListNode* clearNode = cur;
		cur = cur->_next;
		free(clearNode);
	}
}
void ListDestory(ListNode** ppHead)//摧毁链表
{
	assert(*ppHead);//确保指针不为空
	ListClear(*ppHead);
	free(*ppHead);//释放头结点
}

6.查找并修改链表的节点的数据

        查找和修改是一起的,实现查找就可以实现 修改链表的值。

ListNode* ListFind(ListNode* phead, LTDataType data)//查找链表
{
	assert(phead);
	ListNode* cur = phead->_next;
	while (cur != phead)
	{
		if (cur->_data == data)
			return cur;
		cur = cur->_next;
	}
	return NULL;//找不到返回NULL
}
void ListTest4()
{
	//查找和修改的测试
	ListNode* pHead = NULL;
	ListInit(&pHead);
	ListPushFront(pHead, 1);
	ListPushFront(pHead, 2);
	ListPushFront(pHead, 3);
	ListPushFront(pHead, 4);
	ListPushFront(pHead, 5);
	ListPushFront(pHead, 6);

	ListNode* node = ListFind(pHead, 3);//在链表中查找
	if (node)
	{
		//修改链表的值
		node->_data = 90;
	}
	ListPrint(pHead);
	ListDestory(&pHead);
}

7.全部代码

//List.h

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int LTDataType;
typedef struct ListNode
{
	struct ListNode* _next;
	struct ListNode* _prev;
	LTDataType _data;
}ListNode;

void ListPushBack(ListNode* phead, LTDataType data);//尾插

void ListPopBack(ListNode* phead);//尾删除
void ListPushFront(ListNode* phead,LTDataType data);//头插
void ListPopFront(ListNode* phead);//头删插
ListNode* BuyListNode(LTDataType data);//申请节点
void ListInit(ListNode** pphead);//初始化链表

void ListInsert(ListNode* pos, LTDataType data);//pos位置之前的插入

void ListErase(ListNode* pos);//pos位置的删除
//清理链表
void ListClear(ListNode* phead);

void ListDestory(ListNode** ppHead);//摧毁链表

void ListPrint(ListNode* phead);//打印链表
ListNode* ListFind(ListNode* phead, LTDataType data);//查找链表

 //List.c

#include"List.h"
void ListPushBack(ListNode* phead, LTDataType data)//尾插
{
	assert(phead);
	ListNode* newNode = BuyListNode(data);//申请新节点

	ListNode* tail = phead->_prev;
	//找尾结点
	//链接新节点和尾结点
	tail->_next = newNode;
	newNode->_prev = tail;
	//与头结点进行链接
	phead->_prev = newNode;
	newNode->_next = phead;
}
void ListPopBack(ListNode* phead)//尾删除
{
	//确保指针不为空
	assert(phead);
	assert(phead->_next != phead);//保留头结点
	//找尾
	ListNode* tail = phead->_prev;
	ListNode* newTail = tail->_prev;//找到新的尾结点
	//删除旧的尾结点
	free(tail);
	//重新链接头尾节点
	newTail->_next = phead;
	phead->_prev = newTail;
}
void ListPushFront(ListNode* phead, LTDataType data)//头插
{
	//确保指针不为空
	assert(phead);
	//申请新的节点
	ListNode* newNode = BuyListNode(data);
	//进行链接
	ListNode* realHead = phead->_next;
	realHead->_prev = newNode;
	newNode->_next = realHead;
	phead->_next = newNode;
	newNode->_prev = phead;
}
void ListPopFront(ListNode* phead)//头删插
{
	//指针存在
	assert(phead);
	//并且确保不能删除头结点
	assert(phead->_next != phead);
	//找到真正的头
	ListNode* realHead = phead->_next;
	ListNode* realHeadNext = realHead->_next;
	//删除头节点
	free(realHead);
	//重新链接
	phead->_next = realHeadNext;
	realHeadNext->_prev = phead;
}
ListNode* BuyListNode(LTDataType data)//申请节点
{
	ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));
	if (newNode == NULL)
	{
		printf("申请空间失败\n");
		exit(-1);
	}
	newNode->_data = data;
	return newNode;
}
void ListInit(ListNode** pphead)//初始化链表
{
	*pphead = BuyListNode(0);
	//申请头结点
	//并且初始化
	(*pphead)->_next = *pphead;
	(*pphead)->_prev = *pphead;
}
void ListPrint(ListNode* phead)//打印链表
{
	assert(phead);
	ListNode* cur = phead->_next;
	while (cur != phead)
	{
		printf("%d ", cur->_data);
		cur = cur->_next;
	}
	printf("\n");
}
void ListInsert(ListNode* pos, LTDataType data)//pos位置之前的插入
{
	assert(pos);//确保指针有效
	ListNode* newNode = BuyListNode(data);//申请节点
	//进行链接
	ListNode* prev = pos->_prev;
	ListNode* next = pos;

	prev->_next = newNode;
	newNode->_prev = prev;

	newNode->_next = next;
	next->_prev = newNode;
}
void ListErase(ListNode* pos)//pos位置的删除
{
	//确保指针有效
	assert(pos);
	ListNode* next = pos->_next;
	ListNode* prev = pos->_prev;
	//删除pos所指向的节点
	free(pos);
	//进行重新链接
	prev->_next = next;
	next->_prev = prev;
}
//清理链表
void ListClear(ListNode* phead)
{
	assert(phead);//确保链表不为空
	assert(phead->_next != phead);//除了确保不清理头结点
	ListNode* cur = phead->_next;

	while (cur != phead)
	{
		ListNode* clearNode = cur;
		cur = cur->_next;
		free(clearNode);
	}
}
void ListDestory(ListNode** ppHead)//摧毁链表
{
	assert(*ppHead);//确保指针不为空
	ListClear(*ppHead);
	free(*ppHead);//释放头结点
}
ListNode* ListFind(ListNode* phead, LTDataType data)//查找链表
{
	assert(phead);
	ListNode* cur = phead->_next;
	while (cur != phead)
	{
		if (cur->_data == data)
			return cur;
		cur = cur->_next;
	}
	return NULL;//找不到返回NULL
}

//test.c

#include"List.h"
void ListTest1()
{
	//尾插尾删的测试代码
	ListNode* pHead = NULL;
	ListInit(&pHead);
	ListPushBack(pHead, 1);
	ListPushBack(pHead, 2);
	ListPushBack(pHead, 3);
	ListPushBack(pHead, 4);
	ListPushBack(pHead, 5);
	ListPushBack(pHead, 6);
	ListPrint(pHead);
	ListPopBack(pHead);
	ListPopBack(pHead);
	ListPopBack(pHead);
	ListPopBack(pHead);
	ListPopBack(pHead);
	ListPopBack(pHead);
	//ListPopBack(pHead);

}
void ListTest2()
{
	//头插头删的测试
	ListNode* pHead = NULL;
	ListInit(&pHead);
	ListPushFront(pHead, 1);
	ListPushFront(pHead, 2);
	ListPushFront(pHead, 3);
	ListPushFront(pHead, 4);
	ListPushFront(pHead, 5);
	ListPushFront(pHead, 6);
	ListPrint(pHead);
	ListPopFront(pHead);
	ListPopFront(pHead);
	ListPopFront(pHead);
	ListPopFront(pHead);
	ListPopFront(pHead);
	ListPopFront(pHead);
	ListPopFront(pHead);

}
void ListTest3()
{
	//Destory和Clear的测试
	ListNode* pHead = NULL;
	ListInit(&pHead);
	ListPushFront(pHead, 1);
	ListPushFront(pHead, 2);
	ListPushFront(pHead, 3);
	ListPushFront(pHead, 4);
	ListPushFront(pHead, 5);
	ListPushFront(pHead, 6);
	ListDestory(&pHead);
}
int main()
{
	ListTest3();
	return 0;
}

 

8.测试代码

void ListTest1()
{
	//尾插尾删的测试代码
	ListNode* pHead = NULL;
	ListInit(&pHead);
	ListPushBack(pHead, 1);
	ListPushBack(pHead, 2);
	ListPushBack(pHead, 3);
	ListPushBack(pHead, 4);
	ListPushBack(pHead, 5);
	ListPushBack(pHead, 6);
	ListPrint(pHead);
	ListPopBack(pHead);
	ListPopBack(pHead);
	ListPopBack(pHead);
	ListPopBack(pHead);
	ListPopBack(pHead);
	ListPopBack(pHead);
	//ListPopBack(pHead);

}
void ListTest2()
{
	//头插头删的测试
	ListNode* pHead = NULL;
	ListInit(&pHead);
	ListPushFront(pHead, 1);
	ListPushFront(pHead, 2);
	ListPushFront(pHead, 3);
	ListPushFront(pHead, 4);
	ListPushFront(pHead, 5);
	ListPushFront(pHead, 6);
	ListPrint(pHead);
	ListPopFront(pHead);
	ListPopFront(pHead);
	ListPopFront(pHead);
	ListPopFront(pHead);
	ListPopFront(pHead);
	ListPopFront(pHead);
	ListPopFront(pHead);

}
void ListTest3()
{
	//Destory和Clear的测试
	ListNode* pHead = NULL;
	ListInit(&pHead);
	ListPushFront(pHead, 1);
	ListPushFront(pHead, 2);
	ListPushFront(pHead, 3);
	ListPushFront(pHead, 4);
	ListPushFront(pHead, 5);
	ListPushFront(pHead, 6);
	ListDestory(&pHead);
}

 

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

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

相关文章

C语言学习之大端小端的数据存储

小端的数据存储&#xff1a;数据低位存在地址低位&#xff0c;数据高位存在地址高位&#xff1b;大端的数据存储&#xff1a;数据低位存在地址高位&#xff0c;数据高位存在地址低位&#xff1b;图例显示&#xff1a; 验证PC是大端还是小端的代码&#xff1a; #include <st…

安全学习DAY145_主机服务器端口扫描蜜罐、WAF识别

信息打点-主机架构&蜜罐识别&WAF识别&端口扫描 文章目录 信息打点-主机架构&蜜罐识别&WAF识别&端口扫描概述-思维导图本节知识点&#xff1a;识别应用服务器&其他服务协议&#xff1a;端口扫描NmapMasscan意外环境&#xff1a; 识别WAF防火墙WAF解…

宝塔Linux面板升级“获取更新包失败”怎么解决?

宝塔Linux面板执行升级命令后失败&#xff0c;提示“获取更新包失败&#xff0c;请稍后更新或联系宝塔运维”如何解决&#xff1f;新手站长分享宝塔面板升级失败的解决方法&#xff1a; 宝塔面板升级失败解决方法 1、使用root账户登录到你的云服务器上&#xff0c;宝塔Linux面…

lab1 utilities

测试和运行 参考大佬 修改grade-lab-util文件中的python为python3xv6.out这个文件的所有者可能是root&#xff0c;需要修改为用户&#xff0c;sudo chown woaixiaoxiao xv6.out 每完成一个函数&#xff0c;执行下面的步骤在Makefile中加入新增的程序$U/_sleep\make qemu&…

git 使用远端代码强制覆盖本地

有时候会遇到这种情景&#xff0c;我们本地的代码不需要了&#xff0c;需要使用远端的代码强制覆盖&#xff0c;这时候可以使用下面的命令 git fetch --all然后再执行下面的命令&#xff0c;重置为远端的代码&#xff0c;即使用远端的代码将本地覆盖 origin/远端分之名 git re…

Map中compute、putIfAbsent、computeIfAbsent、merge、computeIfPresent使用

目录 putIfAbsent computeIfAbsent computeIfPresent compute merge putIfAbsent 解释&#xff1a;【不存在则添加】&#xff0c;如果map中没有该key&#xff0c;则直接添加&#xff1b;如果map中已经存在该key&#xff0c;则value保持不变 default V putIfAbsent(K key,…

Metasploitable2靶机漏洞复现

一、信息收集 nmap扫描靶机信息 二、弱口令 1.系统弱口令 在Kali Linux中使用telnet远程连接靶机 输入账号密码msfadmin即可登录 2.MySQL弱口令 使用mysql -h 靶机IP地址即可连接 3.PostgreSQL弱密码登录 输入psql -h 192.168.110.134 -U postgres 密码为postgres 输入\…

Python中的lambda函数

前言 嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! Python中的lambda函数 在Python中&#xff0c;我们使用lambda关键字来声明一个匿名函数&#xff0c; 这就是为什么我们将它们称为“lambda函数”。 匿名函数是指没有声明函数名称的函数。 尽管它们在语法上看起来不同&a…

【MySQL】并发执行事务可能存在的问题, 事物的四种隔离级别

文章目录 前言一、并发执行事务可能存在的问题1, 脏读问题2, 不可重复读3, 幻读 二、MySQL 的四种隔离级别1, READ UNCOMMITTED 读未提交2, READ COMMITTED 读已提交3, REPEATABLE READ 可重复读 (MySQL 的默认事务隔离级别)4, SERIALIZABLE 串行化 总结 前言 各位读者好, 我是…

百度智能云“千帆大模型平台”升级,大模型最多,Prompt模板最全

1、前言 从ChatGPT正式推出之后&#xff0c;大模型开始逐渐火爆起来&#xff0c;基于大模型的潜力与广泛应用前景&#xff0c;多个厂商也开始在大模型领域进行深耕布局。越来越多的人也开始尝试使用大模型来解决日常工作或生活中的问题&#xff0c;有效地提高了处理问题的效率。…

探讨uniapp的生命周期问题

在uniapp中,生命周期函数分为应用生命周期函数、页面生命周期函数和组件生命周期函数. 1应用声明周期 应用生命周期函数只能在 App.vue 中监听有效&#xff0c;在其他页监听无效。 onLaunch&#xff1a;当uni-app 初始化完成时触发&#xff08;全局只触发一次&#xff09;on…

【变形金刚03】使用 Pytorch 开始构建transformer

一、说明 在本教程中&#xff0c;我们将使用 PyTorch 从头开始构建一个基本的转换器模型。Vaswani等人在论文“注意力是你所需要的一切”中引入的Transformer模型是一种深度学习架构&#xff0c;专为序列到序列任务而设计&#xff0c;例如机器翻译和文本摘要。它基于自我注意机…

【传输层】Tcp协议的原理(二)

文章目录 一、TCP协议原理&#xff08;二&#xff09;总结 一、TCP协议原理&#xff08;二&#xff09; 1.解决TIME_WAIT状态引起的bind失败的方法 我们之前实现tcp服务器的时候发现&#xff0c;服务器经常有时候断开可以立即重启&#xff0c;有时候断开必须换端口号才能重新…

qt事件系统源码-----定时器

qt定时器的使用一般有以下几种方式&#xff1a; 1、直接使用QTimer对象&#xff0c;绑定定时器的timeout信号&#xff1b; 2、使用QTimer的静态方法singleshot方法&#xff0c;产生一个一次性的定时事件 3、在QObject子类中&#xff0c;调用startTimer方法&#xff0c;产生定…

Vue.js从入门到精通:软件开发视频大讲堂

&#x1f482; 个人网站:【工具大全】【游戏大全】【神级源码资源网】&#x1f91f; 前端学习课程&#xff1a;&#x1f449;【28个案例趣学前端】【400个JS面试题】&#x1f485; 寻找学习交流、摸鱼划水的小伙伴&#xff0c;请点击【摸鱼学习交流群】 前言 随着Web应用程序的…

nodejs+vue+elementui+express旅游出行指南网站_655ms

本文主要介绍了一种基于windows平台实现的旅游出行指南。该系统为用户找到景点信息和酒店信息提供了更安全、更高效、更便捷的途径。本系统有两个角色&#xff1a;管理员和用户&#xff0c;要求具备以下功能&#xff1a; &#xff08;1&#xff09;用户可以浏览主页了解旅游出行…

2023牛客暑期多校训练营8-C Clamped Sequence II

2023牛客暑期多校训练营8-C Clamped Sequence II https://ac.nowcoder.com/acm/contest/57362/C 文章目录 2023牛客暑期多校训练营8-C Clamped Sequence II题意解题思路代码 题意 解题思路 先考虑不加紧密度的情况&#xff0c;要支持单点修改&#xff0c;整体查询&#xff0…

阿里云服务器安装AMH面板建站教程

本文阿里云百科分享使用阿里云服务器安装AMH面板建站教程&#xff0c;AMH是一套通过Web控制和管理Linux服务器以及虚拟主机的管理系统。您可以使用云服务器ECS安装AMH来搭建PHP网站。本篇教程分别介绍如何在Linux系统实例中部署AMH并快速搭建PHP网站。 目录 前提条件 手动部…

Electron 应用实现截图并编辑功能

Electron 应用实现截图并编辑功能 Electron 应用如何实现截屏功能&#xff0c;有两种思路&#xff0c;作为一个框架是否可以通过框架实现截屏&#xff0c;另一种就是 javaScript 结合 html 中画布功能实现截屏。 在初步思考之后&#xff0c;本文优先探索使用 Electron 实现截屏…

CCF-CSP 29次 第三题【202303-3 LDAP】(多个STL+递归)

计算机软件能力认证考试系统 #include <iostream> #include <cstring> #include <algorithm> #include <vector> #include <unordered_map> #include <string>using namespace std;typedef long long LL;const int N 2510, M 510;int n…