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

news2024/11/25 16:22:56

目录

  • 前言:
  • 一、介绍带头双向循环链表
    • 1.带头双向循环链表的结构
    • 2.带头双向循环链表的功能
  • 二、实现带头双向循环链表
    • 1.创建节点的结构
    • 2.函数的声明
    • 2.函数的实现
      • (1)创建一个新节点
      • (2)初始化哨兵位(带头)节点
      • (3)打印链表
      • (4)尾插
      • (5)尾删
      • (6)头插
      • (7)头删
      • (8)查找
      • (9)在pos位置前插入
      • (10)在pos位置删除
      • (11)销毁
  • 三、全部代码
    • 1.List.h
    • 2.List.c
    • 3.Test.c

前言:

之前我们已经学习了单链表,有了单链表的基础,现在开始学习带头双向循环链表~

一、介绍带头双向循环链表

1.带头双向循环链表的结构

结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单。

在这里插入图片描述

2.带头双向循环链表的功能

单链表可以实现对数据的增删查改,带头双向循环链表也同样能做到,而且实现起来比单链表简单得多。

二、实现带头双向循环链表

1.创建节点的结构

一个节点的结构:

存放数据:data
前指针:prev
后指针:next

typedef struct ListNode
{
	struct ListNode* next;
	struct ListNode* prev;
	LTDataType data;
}LTNode;

2.函数的声明

//创建一个新节点
LTNode* BuyListNode(LTDataType x);
//初始化哨兵位节点
LTNode* LTInit();
//打印
void LTPrint(LTNode* phead);
//尾插
void LTPushBack(LTNode* phead, LTDataType x);
//尾删
void LTPopBack(LTNode* phead);
//头插
void LTPushFront(LTNode* phead, LTDataType x);
//头删
void LTPopFront(LTNode* phead);
//查找
LTNode* LTFind(LTNode* phead, LTDataType x);
//在pos位置前插入
void LTInsert(LTNode* pos, LTDataType x);
//在pos位置删除
void LTErase(LTNode* pos);
//销毁
void LTDestroy(LTNode* phead);

2.函数的实现

(1)创建一个新节点

这里与单链表的实现的方式是一样的,多了一个前指针(prev)。

LTNode* BuyListNode(LTDataType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	newnode->next = NULL;
	newnode->prev = NULL;
	newnode->data = x;
	return newnode;
}

(2)初始化哨兵位(带头)节点

刚开始给头指针初始化为哨兵位节点,方便后面可以直接连接新节点。不需要用二级指针

LTNode* LTInit()
{
	LTNode* phead = BuyListNode(0);
	phead->next = phead;
	phead->prev = phead;
	return phead;
}

(3)打印链表

定义一个指针变量cur为哨兵位的下一个节点,也就是第一个节点。然后遍历链表,只要cur不是phead(哨兵位),就打印节点的数据。

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

(4)尾插

双向链表的尾插比单链表要简单多了。单链表尾插新节点要从头开始遍历找尾,双向链表的尾就是哨兵位的前一个节点。然后尾节点与新节点连接,新节点与哨兵位连接就行了。

刚开始没有节点(除了哨兵位)也是一样的

在这里插入图片描述

void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* tail = phead->prev;
	LTNode* newnode = BuyListNode(x);
	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = phead;
	phead->prev = newnode;
}

(5)尾删

这里要多断言一句代码,哨兵位的下一个节点不能等于它自己,因为只有一个哨兵位就说明没有其他节点,不能再删了。
还是定义一个指针变量为尾节点,再定义一个为尾节点的前一个节点,free释放掉尾节点,然后尾节点的前一个节点与哨兵位连接起来。
在这里插入图片描述

void LTPopBack(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);
	LTNode* tail = phead->prev;
	LTNode* tailPrev = tail->prev;
	free(tail);
	tailPrev->next = phead;
	phead->prev = tailPrev;
}

(6)头插

定义一个指针变量first为哨兵位的下一个节点(可能是空),然后新节点与first连接,first与哨兵位连接,思路和单链表的头插如出一辙。
在这里插入图片描述

void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* first = phead->next;
	LTNode* newnode = BuyListNode(x);
	phead->next = newnode;
	newnode->prev = phead;
	newnode->next = first;
	first->prev = newnode;
}

(7)头删

与尾删相同,为了防止没有节点了(除了哨兵位)不能再删,所以要对哨兵位下一个节点不能为自己就行断言。
定义一个变量first为第一个节点,另一个变量second为第二个节点(如果只有一个节点,second指向的是哨兵位),free释放first,然后哨兵位与second连接。
在这里插入图片描述

void LTPopFront(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);
	LTNode* first = phead->next;
	LTNode* second = first->next;
	free(first);
	phead->next = second;
	second->prev = phead;
}

(8)查找

与单链表的查找基本相同,从哨兵位的下一个节点开始找,当循环到哨兵位就停下,找到返回这个节点,否则返回NULL

LTNode* LTFind(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

(9)在pos位置前插入

首先要对pos进行断言,判断除了哨兵位是否还有其他节点。
定义一个变量posPrev为pos的前一个节点,然后posPrev与新节点连接,新节点与pos连接。
在这里插入图片描述

void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);
	LTNode* newnode = BuyListNode(x);
	LTNode* posPrev = pos->prev;
	posPrev->next = newnode;
	newnode->prev = posPrev;
	newnode->next = pos;
	pos->prev = newnode;
}

(10)在pos位置删除

定义两个变量分别为pos前后的节点,free释放pos,连接两个前后节点
在这里插入图片描述

void LTErase(LTNode* pos)
{
	assert(pos);
	LTNode* posPrev = pos->prev;
	LTNode* posNext = pos->next;
	free(pos);
	posPrev->next = posNext;
	posNext->prev = posPrev;
}

(11)销毁

void LTDestroy(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		LTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);
}

三、全部代码

1.List.h

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int LTDataType;
typedef struct ListNode
{
	struct ListNode* next;
	struct ListNode* prev;
	LTDataType data;
}LTNode;
//创建一个新节点
LTNode* BuyListNode(LTDataType x);
//初始化哨兵位节点
LTNode* LTInit();
//打印
void LTPrint(LTNode* phead);
//尾插
void LTPushBack(LTNode* phead, LTDataType x);
//尾删
void LTPopBack(LTNode* phead);
//头插
void LTPushFront(LTNode* phead, LTDataType x);
//头删
void LTPopFront(LTNode* phead);
//查找
LTNode* LTFind(LTNode* phead, LTDataType x);
//在pos位置前插入
void LTInsert(LTNode* pos, LTDataType x);
//在pos位置删除
void LTErase(LTNode* pos);
//销毁
void LTDestroy(LTNode* phead);

2.List.c

#include "List.h"
//创建一个新节点
LTNode* BuyListNode(LTDataType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	newnode->next = NULL;
	newnode->prev = NULL;
	newnode->data = x;
	return newnode;
}
//初始化哨兵位节点
LTNode* LTInit()
{
	LTNode* phead = BuyListNode(0);
	phead->next = phead;
	phead->prev = phead;
	return phead;
}
//打印
void LTPrint(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	printf("phead<=>");
	while (cur != phead)
	{
		printf("%d<=>", cur->data);
		cur = cur->next;
	}
	printf("\n");
}
//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* tail = phead->prev;
	LTNode* newnode = BuyListNode(x);
	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = phead;
	phead->prev = newnode;
}
//尾删
void LTPopBack(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);
	LTNode* tail = phead->prev;
	LTNode* tailPrev = tail->prev;
	free(tail);
	tailPrev->next = phead;
	phead->prev = tailPrev;
}
//头插
void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* first = phead->next;
	LTNode* newnode = BuyListNode(x);
	phead->next = newnode;
	newnode->prev = phead;
	newnode->next = first;
	first->prev = newnode;
}
//头删
void LTPopFront(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);
	LTNode* first = phead->next;
	LTNode* second = first->next;
	free(first);
	phead->next = second;
	second->prev = phead;
}
//查找
LTNode* LTFind(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}
//在pos位置前插入
void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);
	LTNode* newnode = BuyListNode(x);
	LTNode* posPrev = pos->prev;
	posPrev->next = newnode;
	newnode->prev = posPrev;
	newnode->next = pos;
	pos->prev = newnode;
}
//在pos位置删除
void LTErase(LTNode* pos)
{
	assert(pos);
	LTNode* posPrev = pos->prev;
	LTNode* posNext = pos->next;
	free(pos);
	posPrev->next = posNext;
	posNext->prev = posPrev;
}
//销毁
void LTDestroy(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		LTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);
}

3.Test.c

#include "List.h"
void test()
{
	LTNode* plist = LTInit();
	LTPushBack(plist, 11);
	LTPushBack(plist, 12);
	LTPushBack(plist, 13);
	LTPushBack(plist, 14);
	LTPushBack(plist, 15);
	LTPrint(plist);

	LTPopBack(plist);
	LTPopBack(plist);
	LTPopBack(plist);
	LTPrint(plist);

	LTPushFront(plist, 99);
	LTPushFront(plist, 89);
	LTPushFront(plist, 79);
	LTPushFront(plist, 69);
	LTPushFront(plist, 59);
	LTPrint(plist);

	LTPopFront(plist);
	LTPopFront(plist);
	LTPrint(plist);

	LTNode* pos1 = LTFind(plist, 11);
	{
		if (pos1)
		{
			LTInsert(pos1, 666);
		}
	}
	LTPrint(plist);

	LTNode* pos2 = LTFind(plist, 89);
	{
		if (pos2)
		{
			LTErase(pos2);
		}
	}
	LTPrint(plist);

	LTDestroy(plist);

}
int main()
{
	test();
	return 0;
}

在这里插入图片描述
感谢观看~

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

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

相关文章

wx原生微信小程序入门常用总结

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、定义值和修改值1、定义值2、修改值&#xff08;1&#xff09;代码&#xff08;2&#xff09;代码说明&#xff08;3&#xff09;注意点 二、点击事件三、微…

油耳朵适合戴什么类型耳机好,适合油耳的无线耳机推荐

传说中的骨传导耳机&#xff0c;相信大家都不陌生吧&#xff01;近年来&#xff0c;这种耳机以其不需要插入耳朵、不会堵塞耳道的特点&#xff0c;在耳机圈内迅速崛起。然而&#xff0c;还有一些人对骨传导耳机望而却步&#xff0c;不知道如何选择适合自己的产品。作为一位骨传…

信息管理系统三级等保的一些要求

一、前言 在做一些互联网系统或面向互联网的系统时&#xff0c;需要进行备案&#xff0c;需要满足网络信息安全维护规章及有关规章制度要求&#xff0c;才能发布到互联网。所以在做系统的需求分析时&#xff0c;往往需要把信息管理系统三级等保的需求加上&#xff0c;方便开发…

Effective C++条款09——绝不在构造和析构过程中调用virtual 函数(构造/析构/赋值运算)

本条款开始前我要先阐述重点:你不该在构造函数和析构函数期间调用virtual函数&#xff0c;因为这样的调用不会带来你预想的结果&#xff0c;就算有你也不会高兴。如果你同时也是一位Java或C#程序员&#xff0c;请更加注意本条款&#xff0c;因为这是CH与它们不相同的一个地方。…

斗鱼财报盈利的背后:左手艳舞、右手擦边

本月14日&#xff0c;直播平台斗鱼发布了其第二季度财报&#xff0c;面对“看起来还不错的数据”&#xff0c;其对外着重强调了“连续两个季度实现盈利”&#xff0c;并称“斗鱼收入结构持续优化”“斗鱼盈利能力提升”“斗鱼稳健增长可期”“督导提升了内容审核能力”。 财报…

【C++STL基础入门】深入浅出string类查找字串、返回字串和交换操作

文章目录 前言一、查找字串二、返回字串三、交换字串四、运算符重载总结 前言 本STL使用VS2022C20版本 C标准库&#xff08;Standard Template Library&#xff0c;简称STL&#xff09;是C中非常强大和常用的一组容器、算法和函数模板&#xff0c;它能大大简化程序开发和提高…

MySQL高级篇——MySQL架构篇2(MySQL的数据目录)

目录 1 MySQL8的主要目录结构1.1 数据库文件的存放路径1.2 相关命令目录1.3 配置文件目录 2 数据库和文件系统的关系2.1 查看默认数据库2.2 数据库在文件系统中的表示2.3.1 InnoDB存储引擎模式2.3.2 MyISAM存储引擎模式 2.4 总结2.5 视图在文件系统中的表示2.6 其他的文件 1 My…

【Linux】vim编辑器

这一趴我们要学习vim编辑器&#xff0c;知道vim是什么、掌握它的3种模式以及学会其常见操作。 目录 Ⅰ. 引入 Ⅱ. 什么是vim&#xff1f; Ⅲ. 3种模式 Ⅳ. 常见操作 命令模式下 复制粘贴组 撤销组 光标定位组 光标移动组 剪切组 切换、替换组 删除组 底行模式下 …

AI+游戏线下沙龙活动暨COC上海城市开发者社区8月活动

引言 近年来&#xff0c;随着人工智能技术的不断发展和游戏开发技术的不断更新&#xff0c;越来越多的游戏公司开始将人工智能技术应用于游戏领域&#xff0c;以提高开发效率、降低成本&#xff0c;实现游戏玩家更好的游戏体验。为了探讨AI游戏的技术实践经验&#xff0c;近日在…

Spring Cloud Alibaba笔记

&#x1f600;&#x1f600;&#x1f600;创作不易&#xff0c;各位看官点赞收藏. 文章目录 Spring Cloud Alibaba 笔记1、Nacos 服务注册和配置中心1.1、Nacos 之下载启动1.2、Nacos 之注册中心1.3、Nacos 之服务发现1.4、Nacos 之配置中心1.5、Nacos 之分类配置1.6、Nacos 之…

【OpenGauss源码学习 —— 执行算子(Result 算子)】

执行算子&#xff08;Result 算子&#xff09; 控制算子Result 算子ExecInitResult 函数ResultState 结构体ExecInitResultTupleSlot 函数ExecAllocTableSlot函数 ExecResult 函数TupleTableSlot 结构体ExecProcNode 函数ExecProcNodeByType 函数ExecProject 函数 ExecEndResul…

docker版jxTMS使用指南:使用jxTMS提供数据

本文讲解了如何jxTMS的数据访问框架&#xff0c;整个系列的文章请查看&#xff1a;docker版jxTMS使用指南&#xff1a;4.4版升级内容 docker版本的使用&#xff0c;请查看&#xff1a;docker版jxTMS使用指南 4.0版jxTMS的说明&#xff0c;请查看&#xff1a;4.0版升级内容 4…

韶音的骨传导耳机怎么样,韶音骨传导耳机是真的骨传导吗

韶音骨传导耳机最为受瞩目的是OpenRun Pro&#xff0c;在发声单元位置上采用了开孔的处理&#xff0c;佩戴上耳的时候发声单元可以贴合耳道&#xff0c;在低频延伸性&#xff0c;但在中高频的时候整体会出现震感&#xff0c;纤细的耳挂在佩戴的时候是有着不错的舒适度的&#x…

Java SpringBoot+Vue 的班级综合测评管理系统的设计与实现(2.0 版本)

文章目录 1. 简介2. 技术栈 3. 需求分析用户需求分析功能需求分析系统性能需求分析 4系统总体设计与实现4.1总体设计 5 系统功能的详细设计与实现5.1 管理员功能模块5.2学生功能模块5.3教师功能模块 源码下载地址 1. 简介 传统的班级综合测评管理系统&#xff0c;一开始都是手工…

Django实现音乐网站 ⒀

使用Python Django框架制作一个音乐网站&#xff0c; 本篇主要是推荐页-推荐排行榜、推荐歌手功能开发。 目录 推荐页开发 推荐排行榜 单曲表增加播放量 表模型增加播放量字段 执行表操作 模板中显示外键对应值 表模型外键设置 获取外键对应模型值 推荐排行榜视图 推…

基于web的鲜花商城系统java jsp网上购物超市mysql源代码

本项目为前几天收费帮学妹做的一个项目&#xff0c;Java EE JSP项目&#xff0c;在工作环境中基本使用不到&#xff0c;但是很多学校把这个当作编程入门的项目来做&#xff0c;故分享出本项目供初学者参考。 一、项目描述 基于web的鲜花商城系统 系统有2权限&#xff1a;前台…

HCIA---访问控制列表

文章目录 目录 前言 一.ACL简介&#xff1a; 二.ACL工作原理 ACL组成&#xff1a; ​编辑 规则编号&#xff1a; ACL匹配规则&#xff1a; 总结 前言 一.ACL简介&#xff1a; ACL全称为Access Control List&#xff0c;即访问控制表&#xff0c;是一种用于控制网络资源访问…

微软韦青:滑向冰球将要到达的位置 | 科创人数智思维私董会第9期回顾

2023年8月5日&#xff0c;由科创人、北航投资联手创办的科创人数智思维私董会第9期圆满举行。 微软&#xff08;中国&#xff09;首席技术官韦青担任本次活动引导嘉宾&#xff0c;近20位来自传统企业、科创企业、投资机构的CEO、技术决策者及领域专家&#xff0c;围绕新人-机时…

【技术篇】• 饮用水除硝酸盐、地下水除砷、矿泉水除溴的技术汇总

我们所说的“自来水”是指从水龙头里放出来的水。但从水龙头里放出来并不等于安全卫生。实际上&#xff0c;原水必须经过各种处理措施之后才能称为安全卫生的饮用水。每一滴水都要经过了混凝、沉淀、过滤、消毒四个步骤的处理&#xff0c;才能去除杂质和细菌&#xff0c;变得安…

恒运资本:简易程序定增是什么意思?

近年来&#xff0c;在我国股市中&#xff0c;简易程序定增成为了一种受欢迎的融资方法。许多人听过它但并不知道它的含义和工作原理。在本文中&#xff0c;我们将从多个角度来分析简易程序定增。 一、什么是简易程序定增&#xff1f; 简易程序定增是指在不需求经过股东大会批阅…