【数据结构初阶(3)】双向带头结点循环链表

文章目录

  • Ⅰ 概念及结构
  • Ⅱ 基本操作实现
    • 1. 结点的定义
    • 2. 创建头节点
    • 3. 创建新结点
    • 4. 双向链表销毁
    • 5. 双向链表打印
    • 6. 双向链表尾插
    • 7. 双向链表尾删
    • 8. 双向链表头插
    • 9. 双向链表头删
    • 10. 双向链表查找
    • 11. 在指定 pos 位置前插入新结点
    • 12. 删除指定 pos 位置的结点
  • Ⅲ 十分钟手搓链表

Ⅰ 概念及结构

  • 双向链表的每一个结点中不仅仅只有指向后继结点的 next 指针,还有指向其前趋结点的 prev 指针。
  • 双向链表的头节点的 prev 指针域指向链表的尾结点,双向链表的尾结点的 next 域指向链表的头结点,因此,在双向链表中不存在指向 NULL 的指针
  • 在带头结点的双向链表中,头节点不存储有效数据。因此,即使链表为空,双向链表中还要有一个头节点,头结点的前趋和后继指针都指向自己

在这里插入图片描述

Ⅱ 基本操作实现

1. 结点的定义

  • 双向循环链表的结点结构应该包含三部分:存储有效数据的数据域、存储前趋结点的前趋指针域、存储后继结点的后继指针域
typedef int LTDataType;		//数据域的数据类型typedef struct ListNode		//双向链表结点
{LTDataType data;		//存储有效数据struct ListNode* prev;	//存储前趋结点struct ListNode* next;	//存储后继结点
}ListNode;

2. 创建头节点

  • 只有一个头结点时,链表是空的。
  • 头结点的前趋和后继都指针头节点本身。

在这里插入图片描述

代码实现

// 创建返回链表的头结点.
ListNode* ListCreate()
{ListNode* head = (ListNode*)malloc(sizeof(ListNode));if (NULL == head){perror("malloc");exit(-1);}head->prev = head;	//头结点的前趋指针指向自己head->next = head;	//头结点的后继指针指向自己return head;		//返回创建好的头结点
}

3. 创建新结点

实现方法

  • 创建除了头结点之外的新结点,这种结点存储有效数据。
  • 在创建新结点时,前趋和后继指针域都不指针任何结点,暂时都为 NULL。

在这里插入图片描述

代码实现

// 创建一个新结点
ListNode* BuySListNode(LTDataType x)
{ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));if (NULL == newnode){perror("malloc");exit(-1);}newnode->data = x;		//新结点数据域存储传来的数据newnode->next = NULL;	//新结点前趋指针暂时指向 NULLnewnode->prev = NULL;	//新结点后继指针暂时指向 NULLreturn newnode;			//返回创建好的新结点
}

4. 双向链表销毁

实现方法

先定义一个 cur 指针指向头结点的后继结点,删除链表时有两种情况。

  1. 链表为空:此时头节点的后继结点就是头结点本身,直接释放头结点即可。
  2. 链表非空:使用一个指针 next 指向 cur 的下一个结点,然后删除 cur 指向的结点,再将 cur 指向 cur 的下一个结点,直到删的只剩头结点为止。

在这里插入图片描述

代码实现

// 双向链表销毁
void ListDestory(ListNode* pHead)
{assert(pHead);ListNode* cur = pHead->next;		//指向头结点的下一个结点while (cur != pHead)			{ListNode* next = cur->next;		//存储当前结点的下一个结点的地址free(cur);						//释放当前结点cur = next;						//让 cur 指向 cur 的下一个结点}free(pHead);						//不管链表开始是不是为空最后都会释放头结点pHead = NULL;
}

5. 双向链表打印

实现方法

  • 定义一个 cur 指针指向头节点的下一个结点 (head->next),输出 cur 指向的结点的数据域的内容,然后让 cur 指向下一个结点。
  • 只有在 cur 指针指向头结点的时候,打印才会结束。如果链表本身为空,则 cur 一开始就会指向头结点,自然什么都不会打印。

代码实现

// 双向链表打印
void ListPrint(ListNode* pHead)
{assert(pHead);					//传过来的不是个空指针ListNode* cur = pHead->next;	//指向头结点的下一个结点(首结点)printf("头结点<->");while (cur != pHead)			//不指向头结点时说明链表中还有结点未遍历到{printf("%d<->", cur->data);	//输出结点数据域的内容cur = cur->next;			//指向当前结点的下一个结点}printf("\n");
}

6. 双向链表尾插

实现方法

在这里插入图片描述

代码实现

// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{assert(pHead);							//传过来的不是个空指针ListNode* newnode = BuySListNode(x);	//先创建要插入的新结点ListNode* tail = pHead->prev;			//找到双向链表的尾结点tail->next = newnode;					//尾结点的后继指针指向新结点newnode->prev = tail;					//新结点的前趋指针指向尾结点newnode->next = pHead;					//新结点的后继结点指向头结点pHead->prev = newnode;					//头结点的前趋指针指向新结点
}

7. 双向链表尾删

实现方法

在这里插入图片描述

代码实现

// 双向链表尾删
void ListPopBack(ListNode* pHead)
{assert(pHead);assert(pHead->next != pHead->prev);	//不是空链表ListNode* tail = pHead->prev;		//找到链表的尾结点ListNode* tail_prev = tail->prev;	//找到尾结点的前一个结点pHead->prev = tail_prev;			//让头结点的前趋指针指向尾结点的前一个结点tail_prev->next = pHead;			//让尾结点的前一个结点的后继指针指向头结点free(tail);							//删除尾结点tail = NULL;
}

8. 双向链表头插

实现方法

  • 定义一个 first 指针指向链表的首结点,之后就随便插入新结点了。
  • 因为已经用 first 指针保存了首结点的地址,所以不用担心会因为插入顺序导致出现 BUG。

在这里插入图片描述

代码实现

// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x)
{assert(pHead);ListNode* newnode = BuySListNode(x);	//创建新结点ListNode* first = pHead->next;			//保存首结点地址pHead->next = newnode;					//头结点后继指向新结点newnode->prev = pHead;					//新结点前趋指向头结点newnode->next = first;					//新结点后继指向首结点first->prev = newnode;					//首结点前趋指向新结点
}

9. 双向链表头删

实现方法

  1. 定义一个 first 指针指向首结点,再定义一个 second 指针指向第二个结点。
  2. 让头结点后继指向第二个结点,让第二个结点前趋指向头结点。
  3. 最后即可删除 first 指向的首结点。

在这里插入图片描述

代码实现

// 双向链表头删
void ListPopFront(ListNode* pHead)
{assert(pHead);assert(pHead->next != pHead->prev);	//链表不为空ListNode* first = pHead->next;		//指向首结点ListNode* second = first->next;		//指向第二个结点pHead->next = second;				//头结点的后继指针指向第二个结点second->prev = pHead;				//第二个结点的前趋指针指向头结点free(first);						//释放首结点first = NULL;
}

10. 双向链表查找

  • 使用一个 cur 指针遍历链表,返回第一个出现要查找的数据的结点的地址。

代码实现

// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x)
{assert(pHead);ListNode* cur = pHead->next;	//链表不为空时指向首结点,链表为空时最后直接返回 NULLwhile (cur != pHead)			//链表还没彻底遍历一遍时继续指向循环体内容{if (cur->data == x)			//结点数据域等于要查找的数{return cur;				//返回出现要查找的数据的结点地址}cur = cur->next;}return NULL;
}

11. 在指定 pos 位置前插入新结点

获取 pos 位置

  • 利用双向链表查找找到要插入的 pos 位置。

实现方法

  • 定义一个 posprev 指针保存 pos 结点的前一个结点,之后可按照任意顺序插入新结点

在这里插入图片描述

代码实现

// 双向链表在 pos 的前面进行插入
void ListInsert(ListNode* pos, LTDataType x)
{assert(pos);							//传过来的不是空指针ListNode* newnode = BuySListNode(x);	//创建新结点ListNode* posprev = pos->prev;			//保存 pos 的前一个结点posprev->next = newnode;				//前一个结点的 next 域指向新结点newnode->prev = posprev;				//新结点的 prev 域指向前一个结点newnode->next = pos;					//新结点的 next 域指向 pos 结点pos->prev = newnode;					//pos 的 prev 域指向新结点
}

功能特点

  • 如果 pos 是头结点的话,那么 pos 的前一个结点就是链表的尾结点,执行该函数就会变成对链表执行尾插

在这里插入图片描述

  • 如果 pos 是首结点的话,执行该函数功能就相当于直接对链表执行头插

在这里插入图片描述

12. 删除指定 pos 位置的结点

获取 pos 位置

  • 利用双向链表查找找到要删除的 pos 位置。

实现方法

  • 定义一个 posprev 指针指向 pos 位置的前一个结点,定义一个 posnext 指针指向 pos 位置的后一个结点。
  • 将 posprev 结点的后继指向 posnext,将 posnext 结点的前趋指向 posprev,最后再删除 pos 结点即可。

在这里插入图片描述

代码实现

// 双向链表删除 pos 位置的节点
void ListErase(ListNode* pos)
{assert(pos);ListNode* posprev = pos->prev;	//保存 pos 位置结点的前趋结点ListNode* posnext = pos->next;	//保存 pos 位置结点的后继结点posprev->next = posnext;		//pos 前一个结点的后继指向 pos 的后一个结点posnext->prev = posprev;		//pos 后一个结点的前趋指向 pos 的前一个结点free(pos);pos = NULL;
}

功能特点

  • 如果 pos 是尾结点,该函数执行的功能就是尾删操作
  • 如果 pos 是首结点,该函数执行的功能就是头删操作

Ⅲ 十分钟手搓链表

  • 根据在指定 pos 位置前插入新结点删除指定 pos 位置的结点,这两个函数的功能特点可以直接对进行函数的头尾插和头尾删功能。
// 双向链表在 pos 的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);// 双向链表删除 pos 位置的节点
void ListErase(ListNode* pos);

1. 指向头尾插功能

  • 头插:直接将 pos 定为首结点即可
ListInsert(pHead->next, xxx);	//首结点为 pos,执行的是头插
  • 尾插:直接将 pos 定为头结点即可
ListInsert(pHead, xxx);			//头结点为 pos,执行的是尾插

2. 执行头尾删功能

  • 头删:直接将 pos 定为首结点即可
ListErase(pHead->next)			//首结点为 pos,执行的是头删
  • 尾删:直接将 pos 定位尾结点即可
ListErase(pHead->prev)			//尾结点为 pos,执行的是尾删

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

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

相关文章

​vmware虚拟机ubuntu系统配置静态ip​

把虚拟机当成服务器&#xff0c;如果虚拟机的ip是一直变化的&#xff0c;每次远程连接需要都修改连接虚拟机的ip地址&#xff0c;这肯定是麻烦的。 一、设置一下本机的VMnet8的ip 配置路径&#xff1a;控制面板->所有控制面板项->网络和共享中心 二、首先设置NAT 选自…

数据结构【DS】树的性质

度为m的树 m叉树 至少有一个节点的度m 允许所有节点的度都<m 一定是非空树&#xff0c;至少有m1个节点 可以是空树 节点数 总度数 1m叉树&#xff1a; 高度为h的m叉树 节点数最少为&#xff1a;h具有n个结点的m叉树 最大高度&#xff1a;n度为m的树&#xff1a; 具有…

Postman的各种参数你都用对了吗?

大家好&#xff0c;我是G探险者。 Postman我们都不陌生&#xff0c;作为一个广泛使用的 HTTP 客户端&#xff0c;平时我们使用它来测试接口&#xff0c;无非就是把接口的url放进去&#xff0c;然后根据请求类型get或者post,在不同位置传一下参数&#xff0c;除了常见的 Params…

Redis(地理空间Geospatial和HyperLogLog)

Geospatial&#xff1a; Redis中的Geospatial提供了一种存储和处理地理空间数据的能力&#xff0c;这对于许多应用非常有用。以下是Redis中的Geospatial的一些作用&#xff1a; 1. 地理位置查询&#xff1a;可以存储地理位置的坐标信息&#xff0c;并且可以通过查询指定半径范…

第2关:图的深度优先遍历

任务要求参考答案评论2 任务描述相关知识编程要求测试说明 任务描述 本关任务&#xff1a;以邻接矩阵存储图&#xff0c;要求编写程序实现图的深度优先遍历。 相关知识 图的深度优先遍历类似于树的先序遍历, 是树的先序遍历的推广&#xff0c;其基本思想如下&#xff1a; …

DITTEL控制器维修SENSITRON6-2AE

DITTEL工控产品维修包括&#xff1a;德国DITTEL平衡测试仪维修,DITTEL模块&#xff0c;过程监控模块&#xff0c;DITTEL控制器&#xff0c;平衡头&#xff0c;机电平衡头&#xff0c;显示器&#xff0c;平衡系统等产品。 DITTEL过程控制模块维修 DM6000是一个过程控制模块&…

onnx模型转换opset版本和固定动态输入尺寸

背景&#xff1a;之前我想把onnx模型从opset12变成opset12&#xff0c;太慌乱就没找着&#xff0c;最近找到了官网上有示例的&#xff0c;大爱onnx官网&#xff0c;分享给有需求没找着的小伙伴们。 1. onnx模型转换opset版本 官网示例&#xff1a; import onnx from onnx im…

element表格第一个列变成最后一个处理方案

解决方案&#xff1a; 翻译过来就是这样子&#xff0c;说el-table不能嵌套el-table-column以外的元素

第3关:图的广度遍历

500 任务要求参考答案评论2 任务描述相关知识编程要求测试说明 任务描述 本关任务&#xff1a;以邻接表存储图&#xff0c;要求编写程序实现图的广度优先遍历。 相关知识 广度优先遍历类似于树的按层次遍历的过程。 假设从图中某顶点v出发&#xff0c;在访问了v之后依次访…

Foodpanda API连接的艺术:无代码开发如何集成营销系统和广告推广工具

连接Foodpanda和电商平台的无代码开发 Foodpanda不仅是一家提供快速外卖服务的国际品牌&#xff0c;而且其创新的技术解决方案还能帮助电商企业优化系统运营。通过无代码开发的方法&#xff0c;即使没有专业的API开发知识&#xff0c;商家也能实现高效的电商系统和客服系统连接…

无人售货奶柜:颠覆传统零售行业的潜力黑马

无人售货奶柜&#xff1a;颠覆传统零售行业的潜力黑马 无人售货奶柜具备体积小、灵活运用空间、无需人工看守和自动结算等特点。相较于传统建店方式&#xff0c;它的成本大大降低&#xff0c;从而提高了运营效率。此外&#xff0c;无人售货奶柜独特的优势之一就是可以保持24小时…

【YOLOX简述】

YOLOX的简述 一、 原因1. 背景2. 概念 二、 算法介绍2.1 YOLOX算法结构图&#xff1a;2.2 算法独特点2.3 Focus网络结构2.4 FPN&#xff0c;PAN2.5 BaseConv2.6 SPP2.7 CSPDarknet2.8 YOlO Head 三、预测曲线3.1 曲线 一、 原因 1. 背景 工业的缺陷检测是计算机视觉中不可缺少…

关于AssetBundle禁用TypeTree之后的一些可序列化的问题

1&#xff09;关于AssetBundle禁用TypeTree之后的一些可序列化的问题 2&#xff09;启动Unity导入变动的资源时&#xff0c;Singleton ScriptableObject 加载不到 3&#xff09;Xcode15构建Unity 2022.3的Xcode工程&#xff0c;报错没有兼容的iPhone SDK 这是第361篇UWA技术知识…

“轻松管理你的文件库:按大小归类保存,高效整理!“

亲爱的朋友们&#xff0c;你是否曾经为了整理电脑中杂乱无章的文件而感到烦恼&#xff1f;文件大小不一&#xff0c;无法快速找到所需内容&#xff0c;实在让人感到心力交瘁。但现在&#xff0c;我们为你带来一种全新的解决方案&#xff0c;让你的文件管理更轻松&#xff0c;更…

使用Mate 40 Harmony OS 4.0版本运行 codelabs ArkUI demo运行非常卡顿,换Mate 60没事

服务类型 DevEco Studio 概述 使用Mate 40 Harmony OS 4.0版本运行 codelabs ArkUI demo运行非常卡顿&#xff0c;换Mate 60没事 官方回复添加链接描述 客户支持工程师 2023-11-21 14:37:19 GMT08:00 尊敬的开发者&#xff0c;您好&#xff0c; 该机型卡顿黑屏为内部已知问题…

Mysql数据库管理-Innodb 内存优化分析

MySql数据库内存分析优化 1 Innodb重做日志 redo log是Innodb保障事务ACID属性的重要机制。工作原理图如下&#xff1a; 2 增加缓冲池数量&#xff0c;减少内部对缓冲池结构争用。 mysql内部线程对innodb缓存池的访问在某个阶段是互斥的&#xff0c;这种内部竞争也会产生性能…

【JavaSE】基础笔记 - 图书管理系统(保姆教程,含源码)

目录 1、图书管理系统介绍 2、大致框架 3、代码实现步骤 3.1、Book图书类 3.2、BookList书架类 3.3、User用户类、AdminUser类、NormalUser类 3.4、IOperation操作接口 3.5、继承IOperation接口的操作类 3.6、完善User类 3.7、Mian类 4、完整代码 Java的三大特性是…

python2环境问题

pip源推荐使用 -i http://pypi.tuna.tsinghua.edu.cn/simple --trusted-host pypi.tuna.tsinghua.edu.cn 安装pip工具&#xff0c;可以环境上已有的pip赋值到对应目录下 更新pip工具&#xff0c;python2的pip需要20.3版本以前的。解决pip命令从其它环境复制过来导致的一系列…

《研发效能 100 问》首发,多位专家解读「研效提升」的破局之道?

为了可以帮助更多研发管理者和研发效能负责人&#xff0c;了解构建研发效能体系应从何做起&#xff0c;以及在构建过程中需要解决哪些疑难问题&#xff0c;有哪些最佳实践可以借鉴。2023 年 7 月&#xff0c;思码逸发起&#xff0c;由行业知名研发效能专家张乐老师担任出品人&a…