【初阶数据结构】深入解析带头双向循环链表:探索底层逻辑

本文主要是介绍【初阶数据结构】深入解析带头双向循环链表:探索底层逻辑,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在这里插入图片描述
🔥引言

本篇将介绍带头双向循环链表底层实现以及在实现中需要注意的事项,帮助各位在使用过程中根据底层实现考虑到效率上问题和使用时可能会导致的错误使用

请添加图片描述
Alt

🌈个人主页:是店小二呀
🌈C语言笔记专栏:C语言笔记
🌈C++笔记专栏: C++笔记
🌈初阶数据结构笔记专栏: 初阶数据结构笔记

🌈喜欢的诗句:无人扶我青云志 我自踏雪至山巅
请添加图片描述

文章目录

  • 一、前文
  • 二、实现带头双向循环链表
    • 2.1 认识头节点
    • 2.2 链表中节点成员
    • 2.3 创建双向循环链表的节点
    • 2.4 双向循环链表的插入节点
      • 2.4.1 双向循环链表的尾插
    • 2.4.2 双向循环链表的头插
    • 2.5 双向循环链表的删除节点
      • 2.5.1 双向循环链表的尾删
      • 2.5.2 双向循环链表的头删
    • 2.6 双向循环链表的查找
    • 2.7 实现双向循环链表任意位置的插入和删除
      • 2.7.1 任意位置插入
      • 2.7.2 任意位置删除
    • 2.8 双向循环链表的打印
  • 三、双向循环链表的好处

一、前文

链表的分类有很多种,只需要将无头单向非循环链表和带头双向循环链表掌握,也就理解了剩下链表构成和实现。带头双向循环链表,结构复杂,一般只用于单独存储数据。但是也由于结构,带来了很多的优势,从而复杂结构,反而简单低实现。

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

2.1 认识头节点

头节点(哨兵位)是指链表里面第一个节点,它不存放任何信息或存储任何有效元素,起到"放哨"作用,作用是减少了对一个节点是否为空的判断。

对于之前实现的单链表是不带哨兵位的,但是称第一个节点为头节点是不规范的,但是那样方便学习中称呼。

2.2 链表中节点成员

首先节点的成员:有效带数值,前驱指针,后继指针

  • 前驱指针:以当前节点为参照物,向左就是前驱指针

  • 后继指针:以当前节点为参照物,向右就是后继指针

typedef int LTDataType;//处理不同的数据类型typedef struct SLTlistNode
{LTDataType val;struct SLTlistNode* next;struct SLTlistNode* prev;}SLNode;

2.3 创建双向循环链表的节点

SLNode* CreatNewNode(LTDataType x)
{SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));if (newnode == NULL){perror("malloc fail!");return ;}newnode->next = newnode;newnode->prev = newnode;newnode->val = x;return newnode;
}

这里需要注意的是:跟实现单链表的节点,大体相同。但是需要前驱指针,后继指针都指向自己设计为一个闭环,在插入中这样子的设计有很好的优势

2.4 双向循环链表的插入节点

2.4.1 双向循环链表的尾插

void SLTPushBack(SLNode* head, LTDataType x)
{assert(head);SLNode* newnode = CreatNewNode(x);SLNode* tail = head->prev;//尾插,需要找到尾tail->next = newnode;newnode->prev = tail;head->prev = newnode;newnode->next = head;
}

在这里插入图片描述

这里需要注意的是:由于一开始哨兵位就是循环体,一开始创建指向尾的指针,也是指向自己,这种结构具有很好的优势,维持闭环的状态,在进行插入或删除操作时,提供了便利。当进行尾插时,需要找到尾,再进行尾插操作。

2.4.2 双向循环链表的头插

void SLTPushFront(SLNode* head, LTDataType x)//双向循环链表无空指针
{assert(head);	if (head->next==head){SLTPushBack(head, x);}else{SLNode* newnode = CreatNewNode(x);SLNode* tail = head->prev;head->next = newnode;newnode->prev = head;newnode->next = tail;tail->prev = newnode;}
}

在这里插入图片描述

这里需要注意的是 : 头插是值第一个有效数据节点前面插入,不是在哨兵位前面进行插入。当只有哨兵位存在时,这里跟尾插操作是一样的,剩下跟单链表的任意位置插入差不多,主要是改变指针的指向

2.5 双向循环链表的删除节点

2.5.1 双向循环链表的尾删

void SLTPopBack(SLNode* head)//哨兵位不删
{assert(head);assert(head->next!=head);SLNode* tail = head->prev;SLNode* tailprev = tail->prev;tailprev->next = head;head->prev = tailprev;free(tail);tail = NULL;
}

在这里插入图片描述

这里需要注意的是:哨兵位不参与对节点进行操作时,对此不对哨兵位进行删除操作。由于循环体虽然没有空指针,但是可能会出现野指针现象。可以不置空free(tail),不对tail指向空间进行访问。

2.5.2 双向循环链表的头删

void SLTPopFront(SLNode* head)
{assert(head);SLNode* cur = head->next;SLNode* curnext = cur->next;if (head->next == head){return 1;}else{head->next = curnext;curnext->prev = head;free(cur);cur = NULL;}
}

在这里插入图片描述

这里需要注意的是:哨兵位不参与对节点进行操作时,对此不对哨兵位进行删除操作。双向循环的优势在此体现出来了,只需在curcurnext位置上处理。

2.6 双向循环链表的查找

SLNode* SLFind(SLNode* head, LTDataType x)
{assert(head);SLNode* cur = head->next;while (cur!=head){if (cur->val == x){return cur;}cur = cur->next;}return NULL;
}

这里需要注意的是:当cur==head时,说明cur遍历完了链表。如果没有符合的值,则表示不存在;反之返回该位置的指针。

2.7 实现双向循环链表任意位置的插入和删除

2.7.1 任意位置插入

void SLInsert(SLNode* head, SLNode* pos,LTDataType x)//之前插入
{assert(head);SLNode* posprve = pos->prev;if (head->next == head){SLTPushBack(head,x);}else{SLNode* newnode = CreateLTNode(x);posprve->next = newnode;newnode->prev = posprve;pos->prev = newnode;newnode->next = pos; }
}

这里需要注意的是:当不存在有效数据时,默认是尾插操作。具体实现逻辑,知道posposprev的地址,再通过改变指针完成链接

在这里插入图片描述

2.7.2 任意位置删除

void SLEmpty(SLNode* head, SLNode* pos, LTDataType x)
{assert(pos != head);assert(head);SLNode* posprve = pos->prev;SLNode* frist = posprve->prev;if (cur->next == head){SLTPopBack(head);}else{frist->next = pos;pos->prev = frist;free(posprve);}
}

在这里插入图片描述

这里需要注意的是:哨兵位不参与对节点进行操作时,对此不对哨兵位进行删除操作。只存在一个有效数据时,只进行尾删操作即可。对于三个指针的位置关系,满足pos在两个指针之间

以上就是双向循环链表的核心接口,接下来实现一些实用小接口,丰富我们的双向循环链表

2.8 双向循环链表的打印

void SLTPrint(SLNode* head)
{assert(head);printf("哨兵位<->");SLNode* cur = head->next;while (cur != head ){printf("%d<->", cur->val);cur = cur->next;}
}

##2.9 双向循环链表的销毁

void SLDestroy(SLNode* head)
{assert(head);SLNode* cur = head->next;//结束条件是什么,这里是无死角的-->先销毁哨兵位之外的空间while (cur!=head){SLNode* curnext = cur->next;free(cur);cur = curnext;}free(head);
}

这里需要注意的是:先将除哨兵位之外的空间释放,最后在释放哨兵位

三、双向循环链表的好处

在实现过程中,我们清晰地知道,如果需要快速搭建一个链表,实现双向循环链表中任意插入、删除是很快的,这里利用到了结构特点


请添加图片描述

以上就是本篇文章的所有内容,在此感谢大家的观看!这里是店小二初阶数据结构笔记,希望对你在学习初阶数据结构中有所帮助!

这篇关于【初阶数据结构】深入解析带头双向循环链表:探索底层逻辑的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1080939

相关文章

C++对象布局及多态实现探索之内存布局(整理的很多链接)

本文通过观察对象的内存布局,跟踪函数调用的汇编代码。分析了C++对象内存的布局情况,虚函数的执行方式,以及虚继承,等等 文章链接:http://dev.yesky.com/254/2191254.shtml      论C/C++函数间动态内存的传递 (2005-07-30)   当你涉及到C/C++的核心编程的时候,你会无止境地与内存管理打交道。 文章链接:http://dev.yesky

如何突破底层思维方式的牢笼

我始终认为,牛人和普通人的根本区别在于思维方式的不同,而非知识多少、阅历多少。 在这个世界上总有一帮神一样的人物存在。就像读到的那句话:“人类就像是一条历史长河中的鱼,只有某几条鱼跳出河面,看到世界的法则,但是却无法改变,当那几条鱼中有跳上岸,进化了,改变河道流向,那样才能改变法则。”  最近一段时间一直在不断寻在内心的东西,同时也在不断的去反省和否定自己的一些思维模式,尝试重

解析 XML 和 INI

XML 1.TinyXML库 TinyXML是一个C++的XML解析库  使用介绍: https://www.cnblogs.com/mythou/archive/2011/11/27/2265169.html    使用的时候,只要把 tinyxml.h、tinystr.h、tinystr.cpp、tinyxml.cpp、tinyxmlerror.cpp、tinyxmlparser.

探索蓝牙协议的奥秘:用ESP32实现高质量蓝牙音频传输

蓝牙(Bluetooth)是一种短距离无线通信技术,广泛应用于各种电子设备之间的数据传输。自1994年由爱立信公司首次提出以来,蓝牙技术已经经历了多个版本的更新和改进。本文将详细介绍蓝牙协议,并通过一个具体的项目——使用ESP32实现蓝牙音频传输,来展示蓝牙协议的实际应用及其优点。 蓝牙协议概述 蓝牙协议栈 蓝牙协议栈是蓝牙技术的核心,定义了蓝牙设备之间如何进行通信。蓝牙协议

探索Elastic Search:强大的开源搜索引擎,详解及使用

🎬 鸽芷咕:个人主页  🔥 个人专栏: 《C++干货基地》《粉丝福利》 ⛺️生活的理想,就是为了理想的生活! 引入 全文搜索属于最常见的需求,开源的 Elasticsearch (以下简称 Elastic)是目前全文搜索引擎的首选,相信大家多多少少的都听说过它。它可以快速地储存、搜索和分析海量数据。就连维基百科、Stack Overflow、

python实现最简单循环神经网络(RNNs)

Recurrent Neural Networks(RNNs) 的模型: 上图中红色部分是输入向量。文本、单词、数据都是输入,在网络里都以向量的形式进行表示。 绿色部分是隐藏向量。是加工处理过程。 蓝色部分是输出向量。 python代码表示如下: rnn = RNN()y = rnn.step(x) # x为输入向量,y为输出向量 RNNs神经网络由神经元组成, python

tf.split()函数解析

API原型(TensorFlow 1.8.0): tf.split(     value,     num_or_size_splits,     axis=0,     num=None,     name='split' ) 这个函数是用来切割张量的。输入切割的张量和参数,返回切割的结果。  value传入的就是需要切割的张量。  这个函数有两种切割的方式: 以三个维度的张量为例,比如说一

【数据结构】线性表:顺序表

文章目录 1. 线性表2. 顺序表2.1 概念及结构2.2 接口实现2.3 顺序表的问题及思考 1. 线性表 线性表是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串… 线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式

数据结构9——排序

一、冒泡排序 冒泡排序(Bubble Sort),顾名思义,就是指越小的元素会经由交换慢慢“浮”到数列的顶端。 算法原理 从左到右,依次比较相邻的元素大小,更大的元素交换到右边;从第一组相邻元素比较到最后一组相邻元素,这一步结束最后一个元素必然是参与比较的元素中最大的元素;按照大的居右原则,重新从左到后比较,前一轮中得到的最后一个元素不参4与比较,得出新一轮的最大元素;按照上述规则,每一轮结

LeetCode--234 回文链表

题目 请判断一个链表是否为回文链表。 示例 示例 1:输入: 1->2输出: false示例 2:输入: 1->2->2->1输出: true /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode(int x) : val