数据结构——带头节点的双向循环列表

news2024/9/21 12:44:09

带头节点的双向循环链表是一种特殊的双向链表,它与普通的双向链表相比,最大的区别是链表头结点的 next 指针不再指向第一个实际节点,而是指向链表中的第一个节点。同时,链表尾结点的 prev 指针也不再指向 NULL,而是指向链表中的最后一个节点。

另外,带头节点的双向循环链表相对于普通的双向链表来说,在插入和删除节点时,需要特别注意头结点和尾结点的指针处理,以保证链表的循环性。
在这里插入图片描述

typedef int LTDatatype;
typedef struct ListNode
{
	struct ListNode* next;
	struct ListNode* prev;
	LTDatatype data;
}LTNode;

这个代码片段定义了一个名为 ListNode 的结构体,包含三个成员:

next:指向链表中下一个节点的指针。
prev:指向链表中上一个节点的指针。
data:保存节点数据的 LTDatatype 类型变量。
typedef int LTDatatype; 语句创建了一个名为 LTDatatype 的 int 数据类型的别名。这使得程序员可以通过更改 typedef 语句而不是修改整个代码,轻松修改链表中使用的数据类型。

总体来说,这段代码简单地定义了一个用于 C 语言双向链表节点的结构体。

//双向列表的初始化
LTNode* LTInit()
{
	LTNode* plist= BuyListNode(-1);
	plist->next = plist;
	plist->prev = plist;
	return plist;
}

这是一个 C 语言函数,用于初始化一个双向链表。函数的返回值是指向链表头结点的指针,函数名为 LTInit,意味着“初始化链表”。

// 双向链表销毁
void ListDestory(LTNode* plist) {
	LTNode* p = plist; // 从头节点开始遍历
	while (p != NULL) {
		LTNode* tmp = p; // 保存当前节点指针
		p = p->next; // 移动到下一个节点
		free(tmp); // 释放当前节点的内存
	}
	plist = NULL; // 将头结点指针置为 NULL
}

这是一个 C 语言函数,用于销毁一个带头节点的双向循环链表。函数的参数是指向链表头结点的指针 plist,函数没有返回值。函数名为 ListDestory,意味着“销毁链表”。

注意,在带头节点的双向循环链表中,头结点的 next 指针不再指向第一个实际节点,而是指向链表中的第一个节点。因此,在销毁链表时,需要从头结点的 next 指针指向的节点开始遍历,直到回到头结点为止。同时,在释放头结点的内存之前,需要先释放链表中的所有节点的内存。

// 双向链表打印
void ListPrint(LTNode* plist)
{
	assert(plist);
	printf("<=head=>");
	LTNode* cur = plist->next;
	while (cur != plist)
	{
		printf("%d<=>", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

这是一个 C 语言函数,用于打印一个带头节点的双向循环链表中所有节点的数据。函数的参数是指向链表头结点的指针 plist,函数没有返回值。函数名为 ListPrint,意味着“打印链表”。

注意,在带头节点的双向循环链表中,头结点的 next 指针不再指向第一个实际节点,而是指向链表中的第一个节点。因此,在打印链表时,需要从头结点的 next 指针指向的节点开始遍历,直到回到头结点为止。同时,在输出节点数据时,需要注意区分头结点和实际节点,以免混淆。

bool LTEmpty(LTNode* plist)
{
    assert(plist != NULL); // 确保链表头结点不为空
    return plist->next == plist; // 如果头结点的 next 指针指向自身,说明链表为空,返回 true,否则返回 false
}

这是一个 C 语言函数,用于判断一个带头节点的双向循环链表是否为空。函数的参数是指向链表头结点的指针 plist,函数返回一个布尔值。函数名为 LTEmpty,意味着“链表是否为空”。

// 双向链表尾删
void ListPopBack(LTNode* plist)
{
	assert(plist);
	assert(!LTEmpty(plist));
	LTNode* tail = plist->prev;
	LTNode* tailPrev = tail->prev;
	tailPrev->next = plist;
	plist->prev = tailPrev;
	free(tail);
	tail = NULL;
}

这是一个 C 语言函数,用于在带头节点的双向循环链表中删除最后一个节点。函数的参数是指向链表头结点的指针 plist,函数没有返回值。函数名为 ListPopBack,意味着“尾删链表”。

函数的实现通常涉及以下步骤:
检查链表头结点和链表是否为空,如果为空,抛出异常。
找到链表中的最后一个节点,即尾结点。
找到尾结点的前驱节点,并将其 next 指针指向头结点,将头结点的 prev 指针指向尾结点的前驱节点。
释放尾结点的内存,并将其指针置为 NULL。

// 双向链表头插
void ListPushFront(LTNode* plist, LTDataType x)
{
    assert(plist != NULL); // 确保链表头结点不为空
    LTNode* newnode = BuyListNode(x); // 创建新节点
    newnode->next = plist->next; // 将新节点的 next 指针指向头结点的下一个节点
    plist->next->prev = newnode; // 将头结点的下一个节点的 prev 指针指向新节点
    plist->next = newnode; // 将头结点的 next 指针指向新节点
    newnode->prev = plist; // 将新节点的 prev 指针指向头结点
}

这是一个 C 语言函数,用于在带头节点的双向循环链表中在头部插入一个新节点。函数的参数是指向链表头结点的指针 plist 和要插入的节点数据 x,函数没有返回值。函数名为 ListPushFront,意味着“头插链表”。

// 双向链表尾插
void ListPushBack(LTNode* plist, LTDataType x)
{
	assert(plist);
	/*LTNode* newnode = BuyListNode(x);
	LTNode* tail = plist->prev;
	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = plist;
	plist->prev = newnode;*/
	ListInsert(plist, x);
}

这是一个 C 语言函数,用于在带头节点的双向循环链表中在尾部插入一个新节点。函数的参数是指向链表头结点的指针 plist 和要插入的节点数据 x,函数没有返回值。函数名为 ListPushBack,意味着“尾插链表”。

函数的实现通常涉及以下步骤:

检查链表头结点是否为空,如果为空,抛出异常。
创建一个新节点,并将其 next 指针指向头结点,将头结点的前驱节点的 next 指针指向新节点。
将新节点的 prev 指针指向头结点的前驱节点,将头结点的 prev 指针指向新节点。

// 双向链表头删
void ListPopFront(LTNode* plist) {
	if (plist == NULL) {
		return; // 如果链表为空,直接返回
	}

	LTNode* p = plist; // 保存头节点指针
	plist = plist->next; // 将下一个节点设置为新的头节点
	if (plist != NULL) {
		plist->prev = NULL; // 如果新的头节点不为空,则将其前驱节点的指针设置为 NULL
	}
	free(p); // 释放原始头节点的内存
}

这是一个 C 语言函数,用于在带头节点的双向循环链表中删除第一个节点。函数的参数是指向链表头结点的指针 plist,函数没有返回值。函数名为 ListPopFront,意味着“头删链表”。

函数的实现通常涉及以下步骤:

检查链表头结点是否为空,如果为空,直接返回。
将头结点的下一个节点设置为新的头节点,并保存原始头节点的指针。
如果新的头节点不为空,则将其前驱节点的指针设置为 NULL。
释放原始头节点的内存,并将其指针置为 NULL。

// 双向链表查找
LTNode* ListFind(LTNode* plist, LTDataType x) 
{
	LTNode* p = plist->next; // 从第一个节点开始遍历
	while (p != NULL && p->data != x) { // 遍历链表,直到找到指定数据或到达链表结尾
		p = p->next; // 移动到下一个节点
	}
	return p; // 返回找到的节点的指针,如果未找到则返回 NULL
}

这是一个 C 语言函数,用于在带头节点的双向循环链表中查找指定数据的节点。函数的参数是指向链表头结点的指针 plist 和要查找的数据 x,函数返回找到的节点的指针,如果未找到则返回 NULL。函数名为 ListFind,意味着“查找链表”。

函数的实现通常涉及以下步骤:

从第一个节点开始遍历链表。
在遍历链表时,如果找到了数据值等于 x 的节点,则返回该节点的指针。
如果遍历到链表的结尾都没有找到符合条件的节点,则返回 NULL。

// 双向链表在pos的前面进行插入
void ListInsert(LTNode* pos, LTDataType x)
{
	assert(pos);
	LTNode* prev = pos->prev;
	LTNode* newnode = BuyListNode(x);
	prev->next = newnode;
	newnode->prev = prev;
	newnode->next = pos;
	pos->prev = newnode;
}

这是一个 C 语言函数,用于在带头节点的双向循环链表中在指定节点的前面插入一个新节点。函数的参数是指向指定节点的指针 pos 和要插入的节点数据 x,函数没有返回值。函数名为 ListInsert,意味着“插入链表”。

函数的实现通常涉及以下步骤:

检查指定节点是否为空,如果为空,抛出异常。
创建一个新节点,并将其 next 指针指向指定节点,将指定节点的前驱节点的 next 指针指向新节点。
将新节点的 prev 指针指向指定节点的前驱节点,将指定节点的 prev 指针指向新节点。

// 双向链表删除pos位置的结点
void ListErase(LTNode* pos) 
{
	if (pos == NULL || pos->prev == NULL) {
		return; // 如果待删除节点为 NULL 或者是头结点,则直接返回
	}

	LTNode* prev_node = pos->prev; // 保存待删除节点的前驱节点
	prev_node->next = pos->next; // 将前驱节点的 next 指针指向待删除节点的后继节点
	if (pos->next != NULL) {
		pos->next->prev = prev_node; // 如果待删除节点有后继节点,则将后继节点的 prev 指针指向前驱节点
	}
	free(pos); // 释放待删除节点的内存
}

这是一个 C 语言函数,用于在带头节点的双向循环链表中删除指定位置的节点。函数的参数是指向指定节点的指针 pos,函数没有返回值。函数名为 ListErase,意味着“删除链表”。

函数的实现通常涉及以下步骤:

检查待删除节点是否为空,如果为空或者是头结点,则直接返回。
保存待删除节点的前驱节点指针。
将前驱节点的 next 指针指向待删除节点的后继节点。
如果待删除节点有后继节点,则将后继节点的 prev 指针指向前驱节点。
释放待删除节点的内存。

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

这是一个 C 语言函数,用于创建一个新的双向链表节点并返回其指针。函数的参数是节点中存储的数据 x,函数返回新节点的指针。函数名为 BuyListNode,意味着“购买链表节点”,通常也可以称为 CreateListNode 或者 NewListNode。

函数的实现通常涉及以下步骤:

分配一个新的节点结构体的内存空间。
将节点的 data 成员赋值为参数 x。
将节点的 next 和 prev 成员指针初始化为 NULL。
返回新节点的指针。

#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
typedef int LTDataType;
typedef struct ListNode
{
	struct ListNode* next;
	struct ListNode* prev;
	LTDataType data;
}LTNode;


//双向列表的初始化
LTNode* LTInit();
// 双向链表销毁
void ListDestory(LTNode* plist);
// 双向链表打印
void ListPrint(LTNode* plist);
// 双向链表尾插
void ListPushBack(LTNode* plist, LTDataType x);
// 双向链表尾删
void ListPopBack(LTNode* plist);
// 双向链表头插
void ListPushFront(LTNode* plist, LTDataType x);
// 双向链表头删
void ListPopFront(LTNode* plist);
// 双向链表查找
LTNode* ListFind(LTNode* plist, LTDataType x);
// 双向链表在pos的前面进行插入
void ListInsert(LTNode* pos, LTDataType x);
// 双向链表删除pos位置的结点
void ListErase(LTNode* pos);

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

//双向列表的初始化
LTNode* LTInit()
{
	LTNode* plist= BuyListNode(-1);
	plist->next = plist;
	plist->prev = plist;
	return plist;
}
// 双向链表销毁
void ListDestory(LTNode* plist) {
	LTNode* p = plist; // 从头节点开始遍历
	while (p != NULL) {
		LTNode* tmp = p; // 保存当前节点指针
		p = p->next; // 移动到下一个节点
		free(tmp); // 释放当前节点的内存
	}
	plist = NULL; // 将头结点指针置为 NULL
}

// 双向链表打印
void ListPrint(LTNode* plist)
{
	assert(plist);
	printf("<=head=>");
	LTNode* cur = plist->next;
	while (cur != plist)
	{
		printf("%d<=>", cur->data);
		cur = cur->next;
	}
	printf("\n");
}


//判断链表是否为空
bool LTEmpty(LTNode* plist)
{
	assert(plist);
	return plist->next == plist;
}

// 双向链表尾删
void ListPopBack(LTNode* plist)
{
	assert(plist);
	assert(!LTEmpty(plist));
	LTNode* tail = plist->prev;
	LTNode* tailPrev = tail->prev;
	tailPrev->next = plist;
	plist->prev = tailPrev;
	free(tail);
	tail = NULL;
}

// 双向链表头插
void ListPushFront(LTNode* plist, LTDataType x)
{
	assert(plist);
	/*LTNode* newnode = BuyListNode(x);
	newnode->next = plist->next;
	plist->next->prev = newnode;
	plist->next = newnode;
	newnode->prev = plist;*/
	ListInsert(plist->next, x);
}

// 双向链表尾插
void ListPushBack(LTNode* plist, LTDataType x)
{
	assert(plist);
	/*LTNode* newnode = BuyListNode(x);
	LTNode* tail = plist->prev;
	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = plist;
	plist->prev = newnode;*/
	ListInsert(plist, x);
}
// 双向链表头删
void ListPopFront(LTNode* plist) {
	if (plist == NULL) {
		return; // 如果链表为空,直接返回
	}

	LTNode* p = plist; // 保存头节点指针
	plist = plist->next; // 将下一个节点设置为新的头节点
	if (plist != NULL) {
		plist->prev = NULL; // 如果新的头节点不为空,则将其前驱节点的指针设置为 NULL
	}
	free(p); // 释放原始头节点的内存
}
// 双向链表查找
LTNode* ListFind(LTNode* plist, LTDataType x) 
{
	LTNode* p = plist->next; // 从第一个节点开始遍历
	while (p != NULL && p->data != x) { // 遍历链表,直到找到指定数据或到达链表结尾
		p = p->next; // 移动到下一个节点
	}
	return p; // 返回找到的节点的指针,如果未找到则返回 NULL
}
// 双向链表在pos的前面进行插入
void ListInsert(LTNode* pos, LTDataType x)
{
	assert(pos);
	LTNode* prev = pos->prev;
	LTNode* newnode = BuyListNode(x);
	prev->next = newnode;
	newnode->prev = prev;
	newnode->next = pos;
	pos->prev = newnode;
}
// 双向链表删除pos位置的结点
void ListErase(LTNode* pos) 
{
	if (pos == NULL || pos->prev == NULL) {
		return; // 如果待删除节点为 NULL 或者是头结点,则直接返回
	}

	LTNode* prev_node = pos->prev; // 保存待删除节点的前驱节点
	prev_node->next = pos->next; // 将前驱节点的 next 指针指向待删除节点的后继节点
	if (pos->next != NULL) {
		pos->next->prev = prev_node; // 如果待删除节点有后继节点,则将后继节点的 prev 指针指向前驱节点
	}
	free(pos); // 释放待删除节点的内存
}

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

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

相关文章

轻松配置深度学习模型 ?

动动发财的小手&#xff0c;点个赞吧&#xff01; 由于所有模块都需要大量参数和设置&#xff0c;因此管理深度学习模型可能很困难。训练模块可能需要诸如 batch_size 或 num_epochs 之类的参数或学习率调度程序的参数。同样&#xff0c;数据预处理模块可能需要 train_test_spl…

Java接口幂等性,如何重试?

Java接口幂等性&#xff0c;如何重试&#xff1f; 文章目录 Java接口幂等性&#xff0c;如何重试&#xff1f;前言一、幂等性是什么&#xff1f;二、为什么要幂等性&#xff1f;三、使用什么办法实现幂等性&#xff1f;1.insert前先select2.加悲观锁3.加乐观锁4.加唯一索引5.Re…

uniapp系列-uni.getAppBaseInfo() versionCode appVersion 值不对应该怎么解决?

今天看到一个BUG 问题描述 我们使用uniapp的官方文档中uni.getAppBaseInfo()后获取的 appVersionCode appVersion &#xff0c;发现获得的结果和我们实际设置的不一致&#xff0c;不是manifest.json里面的值&#xff0c;如下图所示官方文档&#xff1a;https://uniapp.dcloud…

还原大师(MD5)

根据题目提示&#xff0c;都猜得到这应该跟MD5的加密形式有关系 我好像还没有具体了解过MD5编码的格式&#xff0c;或许本题可以通过MD5的编码格式推导出字符串 但是说实话&#xff0c;MD5的加密方式没有找到详细简介的文章 然后我就去网上百度了一下&#xff0c;经过大佬wp的洗…

java springboot整合MyBatis演示增删查改操作

前面我的文章 java springboot整合MyBatis做数据库查询操作讲述了整合springboot整合MyBatis 做了根据id查询的语句 那么 我们现在按它搭建的项目继续 我们在staffDao中添加一个insert函数 参考代码如下 Insert("insert into staff(name, age, status, departmentid) va…

chatgpt赋能python:Python编程实现1+22+333,解密方法

Python编程实现122333&#xff0c;解密方法 在Python编程开发中&#xff0c;我们经常需要求解不同类型的算数表达式&#xff0c;其中求解一系列类似122333的表达式是一个比较常见的需求。本文将会介绍如何使用Python语言方便地求解这类表达式&#xff0c;为大家提供一种针对此…

Redis中AOF和RDB

在Redis的持久化中&#xff0c;常使用的两个手段便是AOF和RDB进行持久化。 RDB&#xff08;Redis DataBase&#xff09;是Redis的持久化方式之一&#xff0c;在配置文件中&#xff0c;我们可以找到 对Redis进行持久化配置&#xff0c;而RDB在持久化时是怎么样进行工作的呢&…

ARM、ARM架构、ARM架构芯片

ARM是一种基于精简指令集&#xff08;RISC&#xff09;的处理器架构&#xff0c;它由英国的ARM公司设计和授权。 ARM芯片具有低功耗、高性能、高集成度等特点&#xff0c;广泛应用于嵌入式系统、移动设备、物联网、服务器等领域。本文将介绍ARM的各类芯片&#xff0c;包括其特…

Java-API简析_java.lang.ClassLoader类(基于 Latest JDK)(浅析源码)

【版权声明】未经博主同意&#xff0c;谢绝转载&#xff01;&#xff08;请尊重原创&#xff0c;博主保留追究权&#xff09; https://blog.csdn.net/m0_69908381/article/details/131345825 出自【进步*于辰的博客】 其实我的【Java-API】专栏内的博文对大家来说意义是不大的。…

CentOS 7.9 安装 Jenkins

CentOS 7.9 安装 Jenkins 文章目录 CentOS 7.9 安装 Jenkins一、概述二、安装1、安装 OpenJDK2、安装 Jenkins3、启动 Jenkins4、给 Jenkins 放行端口 三、初始化 Jenkins 配置1、访问2、解锁 Jenkins3、配置清华大学的源地址4、安装插件5、创建管理员用户6、完成安装 四、功能…

TypeScript ~ TS 掌握自动编译命令 ③

作者 : SYFStrive 博客首页 : HomePage &#x1f4dc;&#xff1a; TypeScript ~ TS &#x1f4cc;&#xff1a;个人社区&#xff08;欢迎大佬们加入&#xff09; &#x1f449;&#xff1a;社区链接&#x1f517; &#x1f4cc;&#xff1a;觉得文章不错可以点点关注 &…

锐捷睿易:云端绑定别人账号,命令方式解绑

一、适用场景 云端绑定了别人的账号&#xff0c;但又不能让他解绑&#xff0c;只能自己解绑从新绑定自己MACC 前提&#xff1a;需要设备联网状态才可以解绑 二、配置步骤 1、登录macc&#xff0c;首页点击设备解绑 2、输入收集的设备序列号&#xff0c;点击获取专属URL&…

chatgpt赋能python:Python清空console的方法

Python清空console的方法 随着Python的应用越来越广泛&#xff0c;我们经常会遇到需要清空Python console的情况。比如&#xff0c;我们可能需要重新开始一段代码的执行&#xff0c;或者想要隐藏过去的交互记录。在这篇文章中&#xff0c;我们将介绍几种方法来清空Python cons…

安装配置nvm-windows对Node.js与npm进行版本控制

一、nvm 由于Node.js版本原因&#xff0c;可能会出现一些错误&#xff0c;如IDEA中Node.js环境下npm报错Error:0308010C:digital envelope routines:unsupported。而且不同的项目&#xff0c;所采用的Node.js的版本不同&#xff0c;重新卸载安装配置&#xff0c;太过繁琐。所以…

chatgpt赋能python:Python求加速度:从计算机视觉到自动驾驶

Python求加速度&#xff1a;从计算机视觉到自动驾驶 在计算机视觉、自动驾驶和机器人等领域&#xff0c;求加速度是常见的任务。Python是一种强大的编程语言&#xff0c;可以用于快速、简便地求解加速度。本篇文章将介绍如何在Python中求解加速度&#xff0c;并探讨加速度在实…

【无标题】很有趣的一个个CSS小球下落动画

代码如下 <!-- 两个div --> <div class"ball"></div> <div class"ground"></div>.ball {width: 30px;height: 30px;background-color: black;border-radius: 50%;position: relative;left: 90px;animation: failing 0.5s ea…

leetcode64. 最小路径和(动态规划-java)

最小路径和 leetcode64. 最小路径和题目描述 暴力递归 缓存代码演示 动态规划代码演示空间压缩代码演示 动态规划专题 leetcode64. 最小路径和 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 链接&#xff1a;https://leetcode.cn/problems/minimum-path-sum 题目描…

C高级6.23

一.整理用户相关的指令、整理磁盘相关的指令 用户相关的指令&#xff1a; 1.id -u 查看用户ID 2.id -g 查看组ID 3.whoami 查看用户名 4.sudo adduser 用户名 创建用户 5.给新用户添加sudo权限 给新用户添加sudo权限的步骤&a…

Spring 与 Servlet-1

整合 Spring 和 Servlet DAO 使用 JDBC 通过模板类进行整合&#xff0c;模板类由 Spring 框架提供&#xff0c;只需进行配置即可 1、依赖&#xff1a;spring-jdbc 和连接池 druid、数据库驱动 mysql-connect-java 2、引入了 IoC、DI 后对象的创建完全交给 Spring 负责&#…

chatgpt赋能python:求1-3+5-7:用Python解决这个算术问题

求1-35-7: 用Python解决这个算术问题 介绍 在Python编程领域中&#xff0c;算术问题是我们经常会遇到的。今天&#xff0c;我们将介绍如何用Python求1-35-7这个算式的结果。这个问题看起来很简单&#xff0c;但如果你是初学者&#xff0c;可能会有点难以理解。不必担心&#…