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

news2025/1/22 12:48:40

在这里插入图片描述

  • 魔王的介绍:😶‍🌫️一名双非本科大一小白。
  • 魔王的目标:🤯努力赶上周围卷王的脚步。
  • 魔王的主页:🔥🔥🔥大魔王.🔥🔥🔥请添加图片描述
    ❤️‍🔥大魔王与你分享:樱花掉落的速度是五厘米 那么两颗心需要多久才能靠近——《秒速五厘米》

文章目录

  • 前言
  • 一、创建结点的结构体
  • 二、创建新结点
  • 三、初始化双向链表(创建哨兵位头节点)
  • 四、双链表销毁
  • 五、判断链表是否为空
  • 六、双向链表打印
  • 七、双向链表尾插
  • 八、双向链表尾删
  • 九、双向链表头插
  • 十、双向链表头删
  • 十一、双向链表查找
  • 十二、双向链表插入
  • 十三、双向链表删除
  • 十五、总代码
    • ListNode.h
    • ListNode.c
    • Test.c
  • 十四、总结

前言

带头双向循环链表是性能非常好的线性表,它在增删查改时不需要很多其他操作,直接就可以进行,虽然实现比其他复杂,但是如果和用起来相比,那双向链表肯定是舒服的,本篇将讲述带头双向循环链表的实现。

  • 本篇链表为双向带头循环链表,所以会用到哨兵位头节点,哨兵位头节点只会用到前(最后一个结点地址)后(第二个结点,也就是存储数据的第一个结点)结点地址,所以只存储前后结点地址,该位置的数据成员不会用到,所以可以给随机值。
  • 哨兵位头结点最大的优势就是不需要动不动就判度是否为空这种分情况,它使得为空和不为空都能用一个代码。看完本篇你会了解到哨兵位头结点的方便。

在这里插入图片描述

一、创建结点的结构体

我们需要让每个结点有三个结构体成员:前后结点地址和存储数据的成员。

//重命名数据类型
typedef int ListNodeDate;

//创建结点的结构体并重命名
typedef struct ListNode
{
	struct ListNode* next;
	struct ListNode* prev;
	ListNodeDate date;
}ListNode;

二、创建新结点

每次增加结点,都需要创建一个新节点(哨兵位头节点也是这样,只不过需要特殊初始化,下面会写)。

//创建新节点
ListNode* BuyNewnode(ListNodeDate x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	newnode->next = NULL;
	newnode->prev = NULL;
	newnode->date = x;
	return newnode;
}

三、初始化双向链表(创建哨兵位头节点)

哨兵位头节点也是结点,需要先调用上面的创建新结点函数,然后再进行特殊的初始化:因为是双向循环链表,所以要让里面存的两个地址都指向自己,这样才能循环,即使没有其他结点。

//初始化双向链表(创建哨兵位头节点)
ListNode* InitListNode()
{
	ListNode* phead = BuyNewnode(-1);
	phead->prev = phead;
	phead->next = phead;
	return phead;
}

四、双链表销毁

创建了链表,而且是动态开辟的,那肯定要手动释放,当我们弄完时手动释放申请的空间。

//双链表的销毁
void DestoryListNode(ListNode* phead)
{
	assert(phead);
	ListNode* cur = phead->next;
	ListNode* next = NULL;
	while (cur!=phead)
	{
		next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);
}
  • 注意出函数后要让指针指向空,因为指向已经释放了,所以是野指针。因为传的是自己(一级指针),而不是自己的指针(二级指针),所以在函数里无法改变实参,只能出函数后再改变指向位置。

五、判断链表是否为空

后面删除元素我们需要先判断是否为空,如果为空(也就是只有哨兵位头结点,没有存储数据的结点),那就没有删除的必要了。

//判断双向链表是否为空(只有哨兵位头节点)
bool EmptyListNode(ListNode* phead)
{
	if (phead->next == phead)
	{
		return true;
	}
	else
		return false;
}

六、双向链表打印

为了验证代码的正确性,需要打印出来看。
打印:从哨兵位头结点指向的下一个结点的数据成员开始打印,直到地址又等于哨兵位头结点,结束。

//双向链表打印
void PrintListNode(ListNode* phead)
{
	assert(phead);
	ListNode* cur = phead->next; 
	while (cur != phead)
	{
		if (cur == phead->next)
			printf("<=>");
		printf("%d<=>",cur->date);
		cur = cur->next;
	}
}

七、双向链表尾插

尾插很简单,直接改变尾部结点、尾插的节点、哨兵位头结点三者之间的指向关系就OK了。

//双向链表尾插
void PushBackListNode(ListNode* phead, ListNodeDate x)
{
	assert(phead);
	ListNode* tail = phead->prev;
	ListNode* newnode = BuyNewnode(x);
	tail->next = newnode;
	newnode->prev = tail;
	phead->prev = newnode;
	newnode->next = phead;
}

八、双向链表尾删

首先判断是否为空,然后开始删,记着释放删除结点的空间。

//双向链表尾删
void PopBackListNode(ListNode* phead)
{
	assert(phead);
	assert(!EmptyListNode(phead));
	ListNode* newtail = phead->prev->prev;
	free(phead->prev);
	newtail->next = phead;
	phead->prev = newtail;
}

九、双向链表头插

改变哨兵位头结点、插入节点和第二个结点的指向关系。

//双向链表头插
void PushFrontListNode(ListNode* phead,ListNodeDate x)
{
	assert(phead);
	ListNode* plist = phead;
	ListNode* newnode = BuyNewnode(x);	
	newnode->next = plist->next;
	newnode->prev = plist;
	plist->next->prev = newnode;
	plist->next = newnode;
}

十、双向链表头删

首先判断是否为空,然后就是改变指向,释放删除的结点空间。

//双向链表头删
void PopFrontListNode(ListNode* phead)
{
	assert(phead);
	assert(!EmptyListNode(phead));
	ListNode* plist = phead;
	ListNode* next = plist->next->next;
	free(plist->next);
	next->prev = plist;
	plist->next = next;
}

十一、双向链表查找

查找某个数值是否在该链表中,并返回其结点的地址。

//双向链表查找
ListNode* FindListNode(ListNode* phead,ListNodeDate x)
{
	ListNode* plist = phead->next;
	assert(!EmptyListNode(plist));
	while (plist != phead)
	{
		if (plist->date == x)
			return plist;
		plist = plist->next;
	}
	return NULL;//找不到或者空链表都返回NULL
}

十二、双向链表插入

在pos位置前插入,也就是正常逻辑的插入,然后我们需要用到上面的那个查找函数,先查找出想要插入的位置,然后插入。

//双向链表在pos前面插入
void InsertListNode(ListNode* phead,ListNodeDate x, ListNode* pos)
{
	assert(phead);
	assert(pos);
	ListNode* ListPrev = pos->prev;
	ListNode* newnode = BuyNewnode(x);
	ListPrev->next = newnode;
	newnode->prev = ListPrev;
	newnode->next = pos;
	pos->prev = newnode;
}

十三、双向链表删除

删除并释放某个数据所对应的结点,首先需要判断是否为空链表,如果只有哨兵位头结点肯定不能删,所以需要判断是否为空链表。

//双向链表删除pos位置结点
void EraseListNode(ListNode* phead, ListNode* pos)
{
	assert(phead);
	assert(pos);
	assert(!EmptyListNode(phead));
	pos->prev->next = pos->next;
	pos->next->prev = pos->prev;
	free(pos);//让pos出函数后置空。
}

十五、总代码

ListNode.h

ListNode.h

#pragma once

//带头双向循环链表

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

//重命名数据类型
typedef int ListNodeDate;

//创建结点的结构体并重命名
typedef struct ListNode
{
	struct ListNode* next;
	struct ListNode* prev;
	ListNodeDate date;
}ListNode;

//创建新节点
ListNode* BuyNewnode(ListNodeDate x);

//初始化双向链表(创建哨兵位头节点)
ListNode* InitListNode();

//双链表的销毁
void DestoryListNode(ListNode* phead);

//双向链表打印
void PrintListNode(ListNode* phead);

//双向链表尾插
void PushBackListNode(ListNode* phead, ListNodeDate x);

//双向链表尾删
void PopBackListNode(ListNode* phead);

//双向链表头插
void PushFrontListNode(ListNode* phead,ListNodeDate x);

//双向链表头删
void PopFrontListNode(ListNode* phead);

//双向链表查找
ListNode* FindListNode(ListNode* phead, ListNodeDate x);

//双向链表在pos前面插入
void InsertListNode(ListNode* phead, ListNodeDate x, ListNode* pos);

//双向链表删除pos位置结点
void EraseListNode(ListNode* phead, ListNode* pos);

ListNode.c

ListNode.c

#define _CRT_SECURE_NO_WARNINGS 1

#include "ListNode.h"

//创建新节点
ListNode* BuyNewnode(ListNodeDate x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	newnode->next = NULL;
	newnode->prev = NULL;
	newnode->date = x;
	return newnode;
}

//初始化双向链表(创建哨兵位头节点)
ListNode* InitListNode()
{
	ListNode* phead = BuyNewnode(-1);
	phead->prev = phead;
	phead->next = phead;
	return phead;
}

//双链表的销毁
void DestoryListNode(ListNode* phead)
{
	assert(phead);
	ListNode* cur = phead;
	ListNode* next = cur->next;
	while (cur!=phead)
	{
		next = cur->next;
		free(cur);
		cur = next;
	}
}

//判断双向链表是否为空(只有哨兵位头节点)
bool EmptyListNode(ListNode* phead)
{
	if (phead->next == phead)
	{
		return true;
	}
	else
		return false;
}

//双向链表打印
void PrintListNode(ListNode* phead)
{
	assert(phead);
	ListNode* cur = phead->next; 
	while (cur != phead)
	{
		if (cur == phead->next)
			printf("<=>");
		printf("%d<=>",cur->date);
		cur = cur->next;
	}
}

//双向链表尾插
void PushBackListNode(ListNode* phead, ListNodeDate x)
{
	assert(phead);
	ListNode* tail = phead->prev;
	ListNode* newnode = BuyNewnode(x);
	tail->next = newnode;
	newnode->prev = tail;
	phead->prev = newnode;
	newnode->next = phead;
}

//双向链表尾删
void PopBackListNode(ListNode* phead)
{
	assert(phead);
	assert(!EmptyListNode(phead));
	ListNode* newtail = phead->prev->prev;
	free(phead->prev);
	newtail->next = phead;
	phead->prev = newtail;
}

//双向链表头插
void PushFrontListNode(ListNode* phead,ListNodeDate x)
{
	assert(phead);
	ListNode* plist = phead;
	ListNode* newnode = BuyNewnode(x);	
	newnode->next = plist->next;
	newnode->prev = plist;
	plist->next->prev = newnode;
	plist->next = newnode;
}

//双向链表头删
void PopFrontListNode(ListNode* phead)
{
	assert(phead);
	assert(!EmptyListNode(phead));
	ListNode* plist = phead;
	ListNode* next = plist->next->next;
	free(plist->next);
	next->prev = plist;
	plist->next = next;
}

//双向链表查找
ListNode* FindListNode(ListNode* phead,ListNodeDate x)
{
	ListNode* plist = phead->next;
	assert(!EmptyListNode(plist));
	while (plist != phead)
	{
		if (plist->date == x)
			return plist;
		plist = plist->next;
	}
	return NULL;//找不到或者空链表都返回NULL
}

//双向链表在pos前面插入
void InsertListNode(ListNode* phead,ListNodeDate x, ListNode* pos)
{
	assert(phead);
	assert(pos);
	ListNode* ListPrev = pos->prev;
	ListNode* newnode = BuyNewnode(x);
	ListPrev->next = newnode;
	newnode->prev = ListPrev;
	newnode->next = pos;
	pos->prev = newnode;
}

//双向链表删除pos位置结点
void EraseListNode(ListNode* phead, ListNode* pos)
{
	assert(phead);
	assert(pos);
	assert(!EmptyListNode(phead));
	pos->prev->next = pos->next;
	pos->next->prev = pos->prev;
	free(pos);//让pos出函数后置空。
}

Test.c

Test.c

#define _CRT_SECURE_NO_WARNINGS 1

#include "ListNode.h"

int main()
{
	//创建哨兵位头节点
	ListNode* plist = InitListNode();//pointer List

	//尾插尾删打印
	//PushBackListNode(plist, 0);
	//PushBackListNode(plist, 1);
	//PushBackListNode(plist, 2);
	//PushBackListNode(plist, 3);
	//PushBackListNode(plist, 4);
	//PopBackListNode(plist);
	//PopBackListNode(plist);
	//PopBackListNode(plist);
	//PrintListNode(plist);
	//头插头删打印
	//PushFrontListNode(plist, 0);
	//PushFrontListNode(plist, 1);
	//PushFrontListNode(plist, 2);
	//PushFrontListNode(plist, 3);
	//PushFrontListNode(plist, 4);
	//PopFrontListNode(plist);
	//PrintListNode(plist);

	//综合验证
	PushBackListNode(plist, 0);
	PushBackListNode(plist, 1);
	PushBackListNode(plist, 2);
	PushFrontListNode(plist, 3);
	PushFrontListNode(plist, 4);
	PushFrontListNode(plist, 5);
	PopBackListNode(plist);
	PopFrontListNode(plist);
	
	//插入
	ListNode* pos = FindListNode(plist,0);
	InsertListNode(plist, 10, pos);

	//删除
	pos = FindListNode(plist, 10);
	EraseListNode(plist,pos);
	pos = NULL;

	PrintListNode(plist);
	DestoryListNode(plist);
	plist = NULL;
	return 0;
}

十四、总结

在这里插入图片描述

✨请点击下面进入主页关注大魔王
如果感觉对你有用的话,就点我进入主页关注我吧!

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

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

相关文章

Rocket 框架基础

Rocket v0.5 DOC Rocket是Rust的一个web框架&#xff0c;它使编写快速、安全的web应用程序变得简单&#xff0c;而不会牺牲灵活性、可用性或类型安全性。 类型安全 从请求到响应&#xff0c;Rocket确保您的类型有意义。样板免费 把时间花在编写真正重要的代码上&#xff0c;让…

Amazon S3 对象存储Java API操作记录(Minio与S3 SDK两种实现)

缘起 今年(2023年) 2月的时候做了个适配Amazon S3对象存储接口的需求&#xff0c;由于4月份自学考试临近&#xff0c;一直在备考就拖着没总结记录下&#xff0c;开发联调过程中也出现过一些奇葩的问题&#xff0c;最近人刚从考试缓过来顺手记录一下。 S3对象存储的基本概念 …

Unity Camera -- (4)探索不同类型的镜头

不同类型的镜头会呈现出不同的氛围和感觉&#xff0c;通常镜头的类型和相机聚焦方式和位置相关。本节我们来看看一些常见的不同类型的镜头。 广角 广角镜头通常在画面中包含更多的环境。观众接受到的是通常从远处拍摄的范围更广的视觉信息。 1. 工程窗口中&#xff0c;在Scene…

04/27课后作业(Qt)

widget.cpp #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);this->setWindowTitle("文件管理"); }Widget::~Widget() {delete ui; }//字体按钮对…

Spring Boot——@Autowired属性注入问题

&#x1f388; Autowired问题 当我们在使用Autowired属性注入时,会发现idea提示Field injection is not recommended ,译为:不推荐使用属性注入   要想了解Spring和idea之所以不推荐使用Autowired属性注入,首先就要先了解Spring常用的注入方式&#xff1a;简单类型注入、集…

基于Redis的分布式限流详解

前言 Redis除了能用作缓存外&#xff0c;还有很多其他用途&#xff0c;比如分布式锁&#xff0c;分布式限流&#xff0c;分布式唯一主键等&#xff0c;本文将和大家分享下基于Redis分布式限流的各种实现方案。 一、为什么需要限流 用最简单的话来说&#xff1a;外部请求是不可…

ArcGIS Pro拓扑

地理数据库拓扑帮助确保数据完整性。拓扑的使用提供了一种对数据执行完整性检查的机制&#xff0c;帮助地理数据库中验证和保持更好的要素表示。 拓扑是点、线和多边形要素共享几何的方式的排列布置。拓扑的用途包括以下几个方面&#xff1a; &#xff08;1&#xff09;限制要…

模型服务,支持渲染多张输出图片|ModelWhale 版本更新

清明时节雨纷纷。晚春的雨季中&#xff0c;ModelWhale 迎来了新一轮的版本更新。 本次更新中&#xff0c;ModelWhale 主要进行了以下功能迭代&#xff1a; • 新增 模型服务多图输出渲染&#xff08;专业版✓ 团队版✓ &#xff09; • 优化 门户成果交流展示&#xff08;团队…

Java异常机制

异常概念 异常是程序在运行期发生的不正常的事件&#xff0c;它会打断指令的正常执行流程。 设计良好的程序应该在异常发生时提供处理这些不正常事件的方法&#xff0c;使程序不会因为异常的发生而阻断或产生不可预见的结果。 Java语言使用异常处理机制为程序提供了异常处理的能…

卡尔曼滤波简介 —— 一维卡尔曼滤波

原文&#xff1a;The alpha - beta - gamma filter (kalmanfilter.net) 一维卡尔曼滤波 在本章中&#xff0c;我们将在一个维度上推导出卡尔曼滤波。本章的主要目标是简单直观地解释卡尔曼滤波的概念&#xff0c;而不使用可能看起来复杂和令人困惑的数学工具。 我们将逐步推进…

oracle connect by 学习

【Connect by 层次查询】 https://www.bilibili.com/video/BV1jV411t7CB/?share_sourcecopy_web&vd_sourced88a617727cccf1c106d623afec0c6b6 简单来说这个connect by 就是为了查父子节点的。 CREATE TABLE test.emp (id varchar(10),name varchar(10),manager_id varch…

Java的位运算

目录 1 Java中支持的位运算 2 位运算规则 3 逻辑运算 3.1 与运算&#xff08;&&#xff09; 3.2 或运算&#xff08;|&#xff09; 3.3 异或运算&#xff08;^&#xff09; 3.3 取反运算&#xff08;~&#xff09; 4 位移操作 4.1 左移&#xff08;<<&#…

Steam-V Rising 私人服务器架设教程

一、安装前的准备 一台服务器 拥有公网IP并且做好了端口映射 二、使用SteamCMD安装服务器 1.下载SteamCMD SteamCMD是Steam专用的命令行式客户端程序&#xff0c;所有的安装方式可以参照&#xff1a;https://developer.valvesoftware.com/wiki/SteamCMD 或者在其他站点自行…

yum、yumdownloader学习

yum命令 https://blog.csdn.net/Netfilter007/article/details/103873293 yum命令是在Fedora和RedHat以及SUSE中基于rpm的软件包管理器。 常用命令 显示&#xff1a;yum list xxx 安装&#xff1a;yum install xxx 升级&#xff1a;yum update xxx 删除&#xff1a;yum remov…

JAVA开发与运维(web生产环境部署)

web生产环境部署&#xff0c;往往是分布式&#xff0c;和开发环境或者测试环境我们一般使用单机不同。 一、部署内容 1、后端服务 2、后台管理系统vue 3、小程序 二、所需要服务器 5台前端服务器 8台后端服务 三、所需要的第三方组件 redismysqlclbOSSCDNWAFRocketMQ…

mac 安装mongodb 无法打开 mongod 无法验证开发者

https://www.mongodb.com/try/download/community 一、下载解压 下载的tgz包&#xff0c;解压到本地 /usr/local 目录下 二、编辑.bash_profile 文件 终端输入命令打开并编辑 .bash_profile 文件 open .bash_profile .bash_profile文件最下面新增一行mongodb相关配置&#x…

leetcode142_环形链表 II

文章目录 题目详情分析Java完整代码 题目详情 给定一个链表的头节点 head &#xff0c;返回链表开始入环的第一个节点。 如果链表无环&#xff0c;则返回 null。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给…

【5天打卡】学习Lodash的第四天——安全漏洞学习

安全漏洞的问题一直是大家关心的问题&#xff0c;仿佛是巧合&#xff0c;在云视频会议服务提供商 Zoom 刚刚被爆出存在”零日漏洞“威胁 Mac 用户隐私和信息安全的同时&#xff0c;开发者熟知的 npm 库 Lodash 也被爆出存在高严重性安全漏洞——”原型污染“漏洞&#xff0c;该…

全网最新版ChatGLM-6B开源模型环境详细部署及安装——如何在低显存单显卡上面安装私有ChatGPT GPT-4大语言模型

目录 前言前期准备电脑要求安装anaconda安装相应版本的CUDA配置ChatGLM-6B Conda环境安装pytorch ChatGLM-6B最新版模型环境部署及安装源码下载模型下载相关库安装运行web演示作为API部署 参考资料其它资料下载 前言 ChatGPT的爆火让许多公司和个人都想要开发自己的大型语言模…

多模态之clip

论文&#xff1a;Learning Transferable Visual Models From Natural Language Supervision Github&#xff1a;https://github.com/OpenAI/CLIP OpenAI出品 论文通过网络爬取4亿(image, text)对&#xff0c;使用对比学习的方法训练得到clip&#xff08;Contrastive Languag…