如何实现双向循环链表

博主主页:17_Kevin-CSDN博客

收录专栏:《数据结构》


引言

双向带头循环链表是一种常见的数据结构,它具有双向遍历的特性,并且在表头和表尾之间形成一个循环。本文将深入探讨双向带头循环链表的结构、操作和应用场景,帮助读者更好地理解和运用这一数据结构。

本篇博客将以图表和代码相结合的方式手撕双向带头循环链表,代码使用C语言进行实现。

1. 结构的定义

双向带头循环链表由多个节点组成,每个节点包含数据域和两个指针域,分别指向前驱节点(prev)和后继节点(next)。在链表的表头和表尾之间会形成一个循环,使得链表可以从任意节点出发进行正向或反向的遍历。

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

通过代码可以感受到,每一个链表节点都包括一个prev和一个next(除了哨兵节点),整体结构的示意图如下:

2. 基本操作

2.1 准备操作

2.1.1 创建新节点

ListNode* BuyListNode(LTDataType x)
{ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));newnode->data = x;newnode->next = NULL;newnode->prev = NULL;return newnode;
}

每个节点都应该具备data,next,prev这三个结构体成员,结构的定义在上文已经进行了描述,所以在创建新节点中直接用ListNode*类型进行新节点的创建。参数x表示要在新节点上插入的数据,在创建新节点后对新节点的成员进行初始化,最后返回ListNode*类型的newnode。

2.1.2 初始化链表 

ListNode* ListInit()
{ListNode* phead = BuyListNode(0);phead->next = phead;phead->prev = phead;return phead;
}

在刚开始的时候需要对链表进行初始化。我们要实现的是一个双向带头循环链表,所以在初始化的时候使哨兵节点的next指向自己,prev指向自己,这样的结构对后面对链表的操作会方便很多,提供了很大的便利。

2.2 遍历操作

2.2.1 打印链表

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

 打印链表不仅可以实现最后链表结果的输出,也可以让我们在进行链表代码书写的时候进行检查所写接口是否有误。

在实现打印链表的时候我们先用一个assert断言来进行判断,如果phead使空的话就会报错停止运行,因为至少要保证有一个表头,要不然无法组成链表。

我们使用一个指针cur来进行访问链表,初始化cur指向phead的next,这样就指向了第一个节点,从第一个节点开始遍历,之后用while循环来进行遍历,每次循环打印当前cur的data,使cur指向cur的next,也就指向了下一个节点。终止的条件是:当cur指向phead的时候终止循环。

2.2.2 查找数据位置

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

首先用assert断言确保该链表不是空的,以保证可以正确查询。定义一个指针cur指向哨兵节点的next(第一个节点),然后循环遍历,直到cur对应的data为要查找的x值的时候停止循环,返回存储x的节点,如果未找到则返回NULL。通过此操作即可找到要查找的数据的位置。

2.3 插入操作

在表头插入的时候有链接新节点的顺序需要注意,有以下两种,第一种为指针方法忽视链接顺序,第二种为直接链接新节点,需要注意链接顺序。

2.3.1.1 在表头插入新节点(first指针)

void ListPushFront(ListNode* phead, LTDataType x)
{assert(phead);ListNode* first = phead->next;ListNode* newnode = BuyListNode(x);// phead newnode firstphead->next = newnode;newnode->prev = phead;newnode->next = first;first->prev = newnode;
}

 2.3.1.2 在表头插入新节点(顺序链接)

void ListPushFront(ListNode* phead, LTDataType x)
{assert(phead);ListNode* newnode = BuyListNode(x);newnode->next = phead->next;phead->next->prev = newnode;phead->next = newnode;newnode->prev = phead;
}

以上为两个表头插入接口函数,明显的区别就是第一种使用first进行保存表头的next,之后在连接的时候使用first就可以进行正常链接。第二种中直接进行链接,但是这种需要注意链接的顺序,因为程序的编译是从上向下进行编译,所以在链接时的顺序不当可能使本该链接的地址被修改覆盖,造成错误的链接,使插入节点新失败。

2.3.2 在表尾插入新节点

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

由于哨兵节点的结构有前驱节点和后继节点,所以在循环带头双向链表中哨兵节点的前驱节点就是最后一个节点的后继节点。我们用tail表示链表的最后一个节点,使tail指向表头的前驱节点,这样就可以快速定位到最后一个节点的next,以便于用来拼接新节点。之后就是使表头和当前的tail与新节点的前驱节点和后继节点进行拼接。

2.3.3 在指定位置插入新节点

// pos位置之前插入x
void ListInsert(ListNode* pos, LTDataType x)
{assert(pos);ListNode* prev = pos->prev;ListNode* newnode = BuyListNode(x);// prev newnode posprev->next = newnode;//pos前的节点的nextnewnode->prev = prev;newnode->next = pos;pos->prev = newnode;
}

用该接口在pos位置前插入新节点。因为NULL没有前后两个指针域,为了避免pos是NULL所以我们使用assert断言进行判断,避免出错。定义一个prev表示pos前的节点,然后用prev链接newnode,再用newnode链接pos,这样就完成了在pos前插入数据了。

2.4 删除操作

2.4.1 删除表头节点

void ListPopFront(ListNode* phead)
{assert(phead);assert(phead->next != phead);ListNode* first = phead->next;ListNode* second = first->next;phead->next = second;second->prev = phead;free(first);first = NULL;
}

该接口中一共使用了两次assert断言:

  1. assert(phead);
  2. assert(phead->next != phead);

 第一个assert用来放置表头为NULL,第二个assert是避免链表不存在数据还进行删除,因为当链表中只存在哨兵节点的时候它的next是指向它自己的,所以使用的条件是phead的next不等于phead。

因为我们是从表头删除节点,所以我们可以先通过哨兵节点找到第一个节点,然后再找到第二个节点。我们的目的是将第一个节点删除,所以我们先定义一个指针first然后用first先暂时存贮第一个节点,然后通过first找到第二个节点,最后再用phead的next与第二个节点进行链接(free掉first节省空间)。如此便实现表头删除节点的接口。

2.4.2 删除表尾节点

void ListPopBack(ListNode* phead)
{assert(phead);assert(phead->next != phead);ListNode* tail = phead->prev;ListNode* prev = tail->prev;prev->next = phead;phead->prev = prev;free(tail);tail = NULL;
}

该接口的两个assert和上方表头删除节点的原理相同,不做过多讲解。

循环链表的表尾就是表头的prev,所以很简单就可以将tail表示出来。再定义一个prev指针用来存储要删除的尾的前一个节点位置。在完成准备工作后我们使用prev的next跳过tail直接指向phead,然后在将phead的prev指向prev。这样就完成了表尾节点的删除,最后用free将之前的表尾节点释放掉就更完美啦!

2.4.3 删除指定位置节点

// 删除pos位置的值
void ListErase(ListNode* pos)
{assert(pos);ListNode* prev = pos->prev;ListNode* next = pos->next;prev->next = next;next->prev = prev;free(pos);
}

该节点用来删除指定位置的节点。

首先使用assert断言保证该位置不是NULL,可以真实进行修改。

例如说,我们要删除d2节点:

按照代码逻辑d2就是传入的参数pos,现在我们定义一个指针prev指向d2的prev,也就是d1,再定义一个指针next指向d2的next,也就是d3。

这样我们就拥有了prev和next两个分别指向目标节点前后节点的指针,然后通过这两个这两个指针将d1和d3进行链接就完成了删除d2的操作,当然,最后将d2给free掉就更完美啦~


通过本文的介绍,我们对双向带头循环链表有了更深入的了解,包括其结构、基本操作、应用场景以及示例代码。双向带头循环链表作为一种重要的数据结构,在实际开发中有着广泛的应用,希望本文能够帮助读者更好地理解和应用这一数据结构。 

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

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

相关文章

谷歌seo推广好还是竞价排名好?

事实上seo跟sem竞价并没有任何冲突,也并没有哪个更好的说法,关键在于理解它们各自的优势与局限性,并根据你的业务,预算来配合 Seo推广的优势在于成本,只要你的网站在搜索结果获得高排名,就能有源源不断的点…

OJ_顺序存储的二叉树

题干 C实现 #define _CRT_SECURE_NO_WARNINGS #include<iostream>using namespace std;int main() {int m, n;while (1) {scanf("%d%d", &m, &n);if (m 0 && n 0) {break;}int i 1;//获取层数&#xff0c;从1开始int begin_level;//存储子…

数据中台:数字中国战略关键技术设施

目录 前言 为何要建设数据中台 数据中台建设痛点 数据中台学习资料 聚焦前沿&#xff0c;方法论体系更新 与时俱进&#xff0c;紧跟时代热点 深入6大行业&#xff0c;提炼实践精华 大咖推荐&#xff0c;数字化转型必备案头书 前言 在数字中国这一国家战略的牵引下&…

精准杜绝医疗设备漏费的智慧防线

19339904493&#xff08;康&#xff09; 医疗设备漏费管理系统&#xff0c;如同医疗设备的智慧守望者&#xff0c;时刻守护着患者的权益与医院的利益。它运用先进的人工智能算法&#xff0c;深度读取设备内部图像与项目信息&#xff0c;通过深度学习图像并分析患者的检查部位、…

招聘系统架构的设计与实现

在当今竞争激烈的人才市场中&#xff0c;有效的招聘系统对企业吸引、筛选和管理人才至关重要。本文将探讨招聘系统的架构设计与实现&#xff0c;帮助企业构建一个高效、可靠的人才招聘平台。 ## 1. 系统架构设计 ### 1.1 微服务架构 招聘系统通常采用微服务架构&#xff0c;将…

Docker制作lamp镜像并在其他机器上部署

为了方便将自己的LAMP运行环境和项目在其他机器上部署或发布&#xff0c;可以用基于Dockerhub 里的mattrayner/lamp镜像打包自己需要的镜像。 1、先选择合适的镜像文件 镜像mattrayner/lamp有多个版本&#xff0c;根据自己需要选择下载 2、镜像在首次运行时会自动下载&#x…

Python算法100例-2.9 舍罕王的失算

完整源代码项目地址&#xff0c;关注博主私信源代码后可获取 1.问题描述2.问题分析3.算法设计4.确定程序框架5.完整的程序6.运行结果 1&#xff0e;问题描述 相传国际象棋是古印度舍罕王的宰相达依尔发明的。舍罕王十分喜爱象棋&#xff0c;决定让宰相自己选择何种赏赐。这位…

自学Python笔记总结(2——了解)

网络了解 网络调试助手 NetAssist.exe NetAssist.exe 使用方法请自行寻找 UDP协议 &#xff08;只能一来一回的的发消息&#xff0c;不可连续发送&#xff09; UDP 是User Datagram Protocol的简称&#xff0c; 中文名是用户数据报协议。在通信开始之前&#xff0c;不需要建…

Jrebel 使用备忘

背景 Java 开发时修改了代码如果手动中止进行然后重启的话&#xff0c;非常麻烦&#xff0c;所以需要一个热部署的插件&#xff0c;修改代码之后即时生效&#xff0c;无需重启。 之前一直用的 devtools&#xff0c;不过在一个新项目中&#xff0c;devtools 有点问题&#xff0…

组合模式(Composite Pattern)C++

上一节&#xff1a;桥接模式&#xff08;Bridge Pattern&#xff09; C 文章目录 0.理论1.目的与应用场景2.实现方式 1.实践 0.理论 组合模式&#xff08;Composite Pattern&#xff09;是一种结构型设计模式&#xff0c;用于将对象组合成树形结构以表示“部分-整体”的层次结…

枚举(蓝桥练习)

目录 一、枚举算法介绍 二、解空间的类型 三、循环枚举解空间 四、例题 &#xff08;一、反倍数&#xff09; &#xff08;二、特别数的和&#xff09; &#xff08;三、找到最多的数&#xff09; &#xff08;四、小蓝的漆房&#xff09; &#xff08;五、小蓝和小桥的…

tkinterFrame框架+标签框架LabelFrame+Toplevel窗口的使用

1.在tkinter中&#xff0c;Frame是一个容器小部件用于组织和管理其他小部件。它可以作为一个独立的可见区域&#xff0c;也可以作为其他小部件的父容器。 import tkinter as tk import tkinter.ttk as ttk import tkinter.messagebox as mbm tk.Tk() m.title("tkinter L…

10.网络游戏逆向分析与漏洞攻防-游戏网络架构逆向分析-接管游戏发送数据的操作

内容参考于&#xff1a;易道云信息技术研究院VIP课 上一个内容&#xff1a;接管游戏连接服务器的操作 码云地址&#xff08;master 分支&#xff09;&#xff1a;染指/titan 码云版本号&#xff1a;00820853d5492fa7b6e32407d46b5f9c01930ec6 代码下载地址&#xff0c;在 ti…

Rocky Linux 运维工具 firewall-cmd

一、firewall-cmd​的简介 ​​firewall-cmd​是基于firewalld的防火墙管理工具。用户可以使用它来配置、监控和管理防火墙规则&#xff0c;包括开放端口、设置服务规则等。 二、firewall-cmd​​的参数说明 序号参数描述1​​–zone指定防火墙区域2–add-portxxx/tcp允许特定…

广和通发布基于MediaTek T300平台的RedCap模组FM330系列及解决方案

世界移动通信大会MWC 2024期间&#xff0c;广和通发布基于MediaTek T300平台的RedCap模组FM330系列&#xff0c;加速5G-A繁荣发展。FM330系列及其解决方案采用全球先进RedCap方案&#xff0c;满足移动宽带和工业互联对高能效的需求。 广和通FM330系列采用全球首款6nm制程且集成…

早产儿视网膜病变分期,自动化+半监督(无需大量医生标注数据)

早产儿视网膜病变 ROP 分期 提出背景解法框架解法步骤一致性正则化算法构建思路 实验 提出背景 论文&#xff1a;https://www.cell.com/action/showPdf?piiS2589-0042%2823%2902593-2 早产儿视网膜病变&#xff08;ROP&#xff09;目前是全球婴儿失明的主要原因之一。 这是…

【DDD】学习笔记-模型对象

不同的建模视角会产生不同的模型&#xff0c;但这并不意味着选择一种建模视角就仅仅会产生一种模型&#xff0c;而是指建模的过程围绕着什么样的模型为核心。领域模型驱动设计自然以领域模型为核心&#xff0c;但在限界上下文内部&#xff0c;分层架构的不同层次仍然可能由不同…

C++:string相关内容的简单介绍

目录 介绍&#xff1a; string类的常见接口说明 1. string类的常见构造 1.1 string(const string& str,size_t pos,size_t len npos); 1.2 string(const char* s) 1.3 string &#xff08;const char * s , size_t n&#xff09;; 1.4 string&#xff08;size_…

图搜索基础-深度优先搜索

图搜索基础-深度优先搜索 参考原理引入流程解析手推例子 代码实现运行结果结果分析 参考 理论参考&#xff1a;深蓝学院 实现参考&#xff1a;github项目 原理 引入 对于这样一个图&#xff0c;我们试图找到S到G的通路&#xff1a; 计算机程序不会像人眼一样&#xff0c;一…

npm淘宝镜像报错certificate has expired

1、概述 vue项目使用npm install命令时&#xff0c;突然报错&#xff1a;“...certificate has expired” 2、解决 1.清空缓存&#xff1a;npm cache clean --force 2.修改镜像&#xff08;管理员运行命令行&#xff09;&#xff1a;npm config set registry https:/…