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

前言 

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

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);
}

 

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

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

相关文章

OpenStack-Y版安装部署

OpenStack-Y版安装部署 目录 OpenStack-Y版安装部署 1、环境准备 1.1 环境简介1.2 配置hosts解析(所有节点)1.3 配置时间同步1.4 安装openstack客户端(控制节点执行)1.5 安装部署MariaDB(控制节点执行)1.6 安装部署RabbitMQ(控制节点执行)1.7 安装部署Memcache(控制节点执行)2、…

JS 解决鼠标悬浮显示弹窗 迅速离开时弹窗显示到其他位置的延迟问题

解决该问题的思路就是&#xff0c;判断当前鼠标的位置是否在某个div上&#xff0c;如果在这个div上则取消显示悬浮弹窗消息。 首先监听鼠标的移动事件 鼠标移动时判断是否在div里面进行移动了 clientX表示鼠标X的位置 client Y表示鼠标Y的位置 拿到要判断的div元素 获取off…

MySQL表的增删查改

目录 一&#xff0c;新增 二&#xff0c;查询 2.1 全列查询 2.2 指定列查询 2.3 查询字段为表达式 2.4 别名 - as 2.5 去重 - distinct 2.6 排序 - order by 2.7 条件查询 - where 2.8 分页查询 - limit 三&#xff0c;修改 - update 四&#xff0c;删除 - delete 一…

学习51单片机怎么开始?

学习的过程不总是先打好基础&#xff0c;然后再盖上层建筑&#xff0c;尤其是实践性的、工程性很强的东西。如果你一定要先全面打好基础&#xff0c;再学习单片机&#xff0c;我觉得你一定学不好&#xff0c;因为你的基础永远打不好&#xff0c;因为基础太庞大了&#xff0c;基…

TOMCAT的多实例部署和动静分离

tomcat的多实例 动静分离 排错小工具&#xff1a; telnet工具&#xff1a;yum -y install telnet telnet工具用于测试端口是否正常 telnet 20.0.0.101 80Tomcat多实例部署&#xff1a; 一台服务器上有多个tomcat的服务 1.安装好 jdk 2.安装 tomcat cd /opt tar zxvf apache-…

c语言——完数的计算

完数即所有因子之和等于其本身值 列入&#xff0c;28124714&#xff0c;28所有的因子为1&#xff0c;2&#xff0c;4&#xff0c;7&#xff0c;14 而这五个因子之和恰好也是28. //完数的计算 /*完数即所有因子之和等于其本身值 列入&#xff0c;28124714&#xff0c;28所有的…

【IMX6ULL驱动开发学习】02.hello驱动程序之cdev注册字符设备驱动程序和设置次设备号

目录 ​编辑 一、register_chrdev 二、解决方法 2.1 alloc_chrdev_region函数&#xff1a;注册一系列字符设备编号 2.2 cdev_init函数&#xff1a;初始化cdev结构体 2.3 cdev_add函数&#xff1a;将字符设备添加到系统中 三、驱动程序 一、register_chrdev major reg…

创建CREATE_STAT_TABLE 统计信息表在达梦和oracle中的使用

达梦 创建CREATE_STAT_TABLE 统计信息表 PROCEDURE CREATE_STAT_TABLE ( STATOWN VARCHAR(128), STATTAB VARCHAR(128), TABLESPACE VARCHAR(128) DEFAULT NULL, GLOBAL_TEMPORARY BOOLEAN DEFAULT FALSE ); 创建普通表的对应系统表的列名字段包括以下&#xff1a; OWNER TABL…

大数据Flink(六十一):Flink流处理程序流程和项目准备

文章目录 Flink流处理程序流程和项目准备 一、Flink流处理程序的一般流程

Fortinet数据中心防火墙及服务ROI超300%!Forrester TEI研究发布

近日&#xff0c;专注网络与安全融合的全球网络安全领导者 Fortinet&#xff08;NASDAQ&#xff1a;FTNT&#xff09;联合全球知名分析机构Forrester发布总体经济影响独立分析报告&#xff0c;详细阐述了在企业数据中心部署 FortiGate 下一代防火墙&#xff08;NGFW&#xff09…

C语言中几种常见数据类型所占字节数

**16位编译器&#xff1a; char/unsigned char &#xff1a;1字节 char &#xff1a;2字节 short int&#xff1a;2字节 int/unsigned int&#xff1a;2字节 long int&#xff1a;4字节 float&#xff1a;4字节 double&#xff1a;8字节* 32位编译器&#xff1a; *char/unsi…

C++ STL vector

目录 一.认识vector 二.vector的使用 1.vector的构造函数 2.vector的迭代器 2.1 begin&#xff08;&#xff09;&#xff0c;end&#xff08;&#xff09; 2.2 rbegin&#xff08;&#xff09;&#xff0c;rend&#xff08;&#xff09; 2.3 迭代器初始化对象 3. vector…

Fairy下载和使用

写在最前&#xff1a;本系列中将会涉及到 Unity&#xff0c;C#&#xff0c;Lua和FairyGUI&#xff08;FGUI&#xff09;。 FairyGUI介绍 官网&#xff1a; FairyGUI 编辑器下载&#xff1a; FairyGUI 截至文档记录最新版&#xff1a; https://res.fairygui.com/FairyGUI-Ed…

现代无人机技术

目录 1.发展 2.应用领域 3.对战争的影响 4.给人类带来的福利 5.给人类带来的坏处 1.发展 无人机的发展可以分为以下几个关键步骤&#xff1a; 1. 早期试验和研究&#xff1a;20世纪初&#xff0c;飞行器的概念开始出现&#xff0c;并进行了一些早期的试飞和实验。这些尝试包…

STM32CubeMX之freeRTOS中断系统

任何中断的优先级都大于任务 优先级是从5-15 而不是0-15 因为前几个已经被freertos所控制了&#xff0c;因为操作系统不是万能的&#xff0c;所以我们需要弄一些中断凌驾在我们操作系统之上&#xff0c;中断中必须使用中断相关的函数&#xff01; 中断不能使用阻塞函数&#…

FinClip 支持小程序维度域名配置;桌面端体验活动进行中

FinClip 的使命是使您&#xff08;业务专家和开发人员&#xff09;能够通过小程序解决关键业务流程挑战&#xff0c;并完成数字化转型的相关操作。不妨让我们看看在本月的产品与市场发布亮点&#xff0c;看看是否有助于您实现目标。 产品方面的相关动向&#x1f447;&#x1f…

初识mysql数据库之引入mysql客户端库

目录 一、下载第三方库 1. 准备工作 1. 使用mysql官网提供的库 2. yum源安装 二、测试第三方库是否可用 三、mysql常用接口介绍 1. 查看官方文档 2. 初始化 3. 关闭mysql 4. 连接mysql 5. 下达sql指令 四、一个简单的C客户端库连接mysql程序 1. 头文件 2. 初始化…

计算机组成原理-笔记-第七章

目录 七、第七章——输入输出系统 1、IO设备与IO控制方式 &#xff08;1&#xff09;控制方式&#xff08;查询&#xff0c;中断&#xff0c;DMA&#xff09; &#xff08;2&#xff09;通道控制 &#xff08;3&#xff09;IO系统 &#xff08;4&#xff09;总结 2、外设…

钕铁硼永磁材料基本概念

目录 一、何为磁性材料二、永磁材料的主要性能三、永磁材料的历史四、永磁材料的分类五、钕铁硼永磁材料5.1 产业链5.2 生产工艺 之前也写过其他行业的一些生产过程和工艺流程&#xff0c;大家有兴趣的可以翻翻以前的文章。 一、何为磁性材料 参加过九年义务教育的同学应该都知…

CSS3 中新增了哪些常见的特性?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 圆角&#xff08;Border Radius&#xff09;⭐ 渐变&#xff08;Gradients&#xff09;⭐ 阴影&#xff08;Box Shadow&#xff09;⭐ 文本阴影&#xff08;Text Shadow&#xff09;⭐ 透明度&#xff08;Opacity&#xff09;⭐ 过渡&…