【数据结构】双向带头循环链表的实现

前言:在前面我们学习了顺序表、单向链表,今天我们在单链表的基础上进一步来模拟实现一个带头双向链表。

💖 博主CSDN主页:卫卫卫的个人主页 💞
👉 专栏分类:数据结构 👈
💯代码仓库:卫卫周大胖的学习日记💫
💪关注博主和博主一起学习!一起努力!
在这里插入图片描述

双向链表

带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表(如下图所示)。通常我们会使用一个头节点head其并不存储数据只是作为一个哨兵位的作用负责指向下一个元素。在这里插入图片描述
双向链表的基本功能:

  1. 双向链表的初始化
  2. 双链表的销毁
  3. 创建一个新的节点
  4. 打印链表中的元素
  5. 双向链表尾插
  6. 双向链表尾删
  7. 双向链表头插
  8. 双向链表的头删
  9. 双链表元素查找
  10. 双向链表在pos的前面进行插入
  11. 双向链表删除pos位置的节点

双向链表的定义

//双向链表的定义
typedef int LTDatatype;typedef struct ListNode
{struct ListNode* next;struct ListNode* prev;LTDatatype val;
}LTNode;

双向链表-创建新的节点

因为我们是动态开辟的链表,因此我们在对链表进行操作的时候,每插入一个节点时都要开辟一个节点,因此我们一样写一个接口函数来实现。
代码思路:我们只需要直接和前面单链表一样开辟思路即可,无非我们需要多管理一个prev指针,这里我们让其置为空即可。
代码实现

LTNode* ListCreate(LTDatatype x)//创建一个新的节点
{LTNode* newnode = malloc(sizeof(LTNode));if (newnode == NULL){perror("malloc fail");return;}newnode->next = NULL;newnode->val = x;newnode->prev = NULL;return newnode;
}

双向链表-初始化

代码思路:因为双向链表中,我们需要一个哨兵位(头节点)来管理,因此我们在初始化的时候,需要开辟一个节点作为哨兵位,然后将其的prenext指针置为空即可。
代码实现:

LTNode* ListInit()//双链表的初始化
{LTNode* phead = ListCreate(-1);phead->next = phead;phead->prev = phead;return phead;
}

双向链表-打印链表中的元素

代码思路:由于我们是一个循环双向链表,在前面的图可以知道,我们不能够直接通过判断尾指针是否为空指针来判定是否是链表中的尾部元素,但是我们可以知道的是:链表中的最后一个节点的下一个节点,是该链表的头部,所以我们通过判定链表中当前节点的下一个节点是不是头节点,就可以知道是否是链表的尾部了。
代码实现:

void ListPrint(LTNode* pHead)//打印链表中的元素
{assert(pHead);LTNode* cur = pHead;printf("哨兵位-> ");while (pHead != cur->next){printf("%d->", cur->next->val);cur = cur->next;}printf("\n");

双向链表-尾插

代码思路:由于双向循环链表的特性,我们可以知道哨兵位的pre指向的是尾部节点,因此我们在尾插的时候不用特意的去寻找尾节点,我们只需要,用哨兵位的前驱指针找到尾部节点,让其指向新开辟的空间。因此:

  1. 通过哨兵位的前驱指针找到尾部节点
  2. 让尾部节点指向新开辟的空间。
  3. 让新开辟的空间的前驱指针指向原理的尾部节点,再让其尾指针指向头节点,即可完成双向链表的尾插。(如下图所示)
    在这里插入图片描述

函数实现

void ListPushBack(LTNode* pHead, LTDatatype x)//双向链表尾插
{assert(pHead);LTNode* newnode = ListCreate(x);LTNode* cur = pHead->prev;//尾结点pHead->prev = newnode;newnode->next = pHead;newnode->prev = cur;cur->next = newnode;
}

函数测试:

void Test_ListPushBack()
{LTNode* sl = ListInit();ListPushBack(sl, 5);ListPushBack(sl, 2);ListPushBack(sl, 1);ListPushBack(sl, 8);ListPrint(sl);
}

运行结果:
在这里插入图片描述


双向链表-尾删

代码思路:这里我们依然要重复利用刚刚说到的循环双链表的性质,我们直接通过哨兵位的前驱指针来找到尾结点,来帮助我们进行尾删。

  1. 通过哨兵位前驱指针找到尾节点
  2. 找到尾结点的前驱指针,即倒数第二个节点,让其指向头节的前驱指针指向倒数第二个节点。
  3. 此时在将倒数第二个节点的尾部节点指向头节点,即可完成尾删,此时在释放掉原本的尾部节点即可。(如下图所示)
    在这里插入图片描述

代码实现:

void ListPopBack(LTNode* pHead)//双向链表尾删
{assert(pHead);assert(pHead->next);LTNode* tail = pHead->prev;//尾部节点pHead->prev = tail->prev;//让头节点指向倒数第二个节点tail->prev->next = pHead;//尾部节点指向头节点free(tail);tail = NULL;
}

函数测试:


void Test_ListPopBack()//双向链表尾删
{LTNode* sl = ListInit();ListPushBack(sl, 5);ListPushBack(sl, 2);ListPushBack(sl, 1);ListPushBack(sl, 8);printf("删除前\n");ListPrint(sl);ListPopBack(sl);printf("删除后\n");ListPrint(sl);
}

运行结果:
在这里插入图片描述


双向链表-头插

代码思路:

  1. 我们将哨兵位的next指针指向新开辟的节点。
  2. 将新节点的前驱指针指向原本的哨兵位
  3. 将新节点的next指向原本的第二个节点
  4. 将原本的第二个节点的前驱指针指向新节点,即可完成头插。(如下图)在这里插入图片描述

代码实现:

void ListPushFront(LTNode* pHead, LTDatatype x)//双向链表头插
{assert(pHead);LTNode* newnode = ListCreate(x);LTNode* tail = pHead->next;//记录头节点的下一个节点的位置pHead->next = newnode;//让头节点的下一个节点指向新节点newnode->prev = pHead;//让新节点的前驱指针指向头节点tail->prev = newnode;//让原本的第二个节点的前驱指针指向newnodenewnode->next = tail;//新节点的尾部节点指针原本的第二个节点
}

函数测试:

void Test_ListPushFront()//双向链表头插
{LTNode* sl = ListInit();ListPushBack(sl, 5);ListPushBack(sl, 2);ListPushBack(sl, 1);ListPushBack(sl, 8);printf("头插前\n");ListPrint(sl);printf("头插后\n");ListPushFront(sl, 10);ListPrint(sl);
}

运行结果
在这里插入图片描述


双向链表-头删

代码思路:

  1. 我们直接通过哨兵位找到头节点,然后将哨兵位的后驱指针指向头节点的下一个节点。
  2. 将头节点的下一个节点的前驱指针指向哨兵位
  3. 在将原本的头节点释放掉即可完成头删(如下图)
    在这里插入图片描述

代码实现:

void ListPopFront(LTNode* pHead)//双向链表的头删
{assert(pHead);assert(pHead->next);LTNode* tail = pHead->next;pHead->next = tail->next;//找到第二个节点指向哨兵位后驱指针tail->next->prev = pHead;//让次节点指向哨兵位free(tail);tail = NULL;
}

函数测试:

void Test_ListPopFront()//双向链表的头删
{LTNode* sl = ListInit();ListPushBack(sl, 5);ListPushBack(sl, 2);ListPushBack(sl, 1);ListPushBack(sl, 8);printf("头删前\n");ListPrint(sl);printf("头删后\n");ListPopFront(sl);ListPrint(sl);
}

运行结果:
在这里插入图片描述


双向链表-元素查找

代码思路:

  1. 这里我们依然通过双向链表的性质来帮助我们判断是否走到了链表尾部。
  2. 我们定义一个cur指针去帮助我们查找
  3. cur碰到了尾部的时候说明查找完了,如果此时还没找到就返回空即可
  4. 在查找的过程中碰到了要查找的值直接返回此时cur的地址即可。(如下图)
    请添加图片描述

代码实现:

LTNode* ListFind(LTNode* pHead, LTDatatype x)//双链表查找
{assert(pHead);LTNode* cur = pHead->next;while (cur != pHead){if (cur->val == x){return cur;}cur = cur->next;}return NULL;
}

函数测试


void Test_ListFind()//双向链表的查找
{LTNode* sl = ListInit();ListPushBack(sl, 5);ListPushBack(sl, 2);ListPushBack(sl, 1);ListPushBack(sl, 8);printf("%p\n", ListFind(sl, 2));printf("%p\n", ListFind(sl, 999));
}

运行结果:
在这里插入图片描述


双向链表-在pos的前面进行插入

代码思路:

  1. 我们找到要插入的pos的前一个节点,让其的next指向新开辟的节点
  2. 在让此时pos前驱指针所在的位置指向新开辟的节点
  3. 再让原本插入的节点的next指向新开辟的节点
  4. 再让新开辟的节点的前驱指针和next分别指向原本pos的前一个节点和指向pos。(如下图)

在这里插入图片描述
代码实现:

void ListInsert(LTNode* pos, LTDatatype x)// 双向链表在pos的前面进行插入
{LTNode* newnode = ListCreate(x);LTNode* cur = pos->prev;//找到pos前的一个节点pos->prev = newnode;//让其前一个结点指向新结点cur->next = newnode;newnode->prev = cur;newnode->next = pos;
}

函数测试:

void Test_ListInsert()
{LTNode* sl = ListInit();ListPushBack(sl, 5);ListPushBack(sl, 2);ListPushBack(sl, 1);ListPushBack(sl, 8);printf("插入前\n");ListPrint(sl);ListInsert(ListFind(sl, 2), 99);printf("插入后\n");ListPrint(sl);
}

运行结果:
在这里插入图片描述


双向链表-删除pos所在的位置

代码思路:

  1. 我们找到pos所在位置的下个一个节点让其指向pos所在位置的前一个结点
  2. 此时在释放掉pos所在的位置即可完成删除

代码实现:

void ListErase(LTNode* pos)// 双向链表删除pos位置的节点
{assert(pos);LTNode* tail = pos->next;tail->prev = pos->prev;pos->prev->next = tail;free(pos);pos = NULL;
}

函数测试:

void Test_ListErase()
{LTNode* sl = ListInit();ListPushBack(sl, 5);ListPushBack(sl, 2);ListPushBack(sl, 1);ListPushBack(sl, 8);printf("删除前\n");ListPrint(sl);printf("删除后\n");ListErase(ListFind(sl, 2));ListPrint(sl);
}

运行结果:
在这里插入图片描述

双向链表-链表的销毁

关于双向链表的销毁这里就不做过多的总结了,这个和前面的打印元素有比较像,因此不懂的可以参考一下即可。
代码实现:

void ListDestory(LTNode* pHead)//双链表的销毁
{assert(pHead);LTNode* cur = pHead->next;while (pHead != cur){LTNode* tail = cur->next;free(cur);cur = tail;}free(pHead);}

总结

到这里我们可以发现,当我们写了一个插入之后会发现,那双向链表的头插和尾插,我们可以直接用我们刚刚写的插入的函数直接来实现,就完全没必要单独写尾插和头插了,至于为什么放在最后才说,是因为作者想和大家一起锻炼一下自己的思维能力,这里直接放代码就不演示了。

void ListPushBack(LTNode* pHead, LTDatatype x)//双向链表尾插
{assert(pHead);ListInsert(pHead,x);//直接再phead之前插入即可
}void ListPushFront(LTNode* pHead, LTDatatype x)//双向链表头插
{assert(pHead);ListInsert(pHead->next, x);
}

结语:今天的内容就到这里吧,谢谢各位的观看,如果有讲的不好的地方也请各位多多指出,作者每一条评论都会读的,谢谢各位。


🫵🫵🫵 这里提前祝各位新年快乐呀! 💞

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

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

相关文章

USB -- STM32F103复合设备(HID+MassStorage)传输讲解(十)

目录 链接快速定位 前沿 1 描述符讲解 1.1 设备描述符 1.2 配置描述符 1.3 接口描述符 1.4 功能描述符 1.5 端点描述符 1.6 字符串描述符 1.7 报告描述符 2 运行演示 链接快速定位 USB -- 初识USB协议(一) 源码下载请参考链接:…

java中PhantomReference WeakReference SoftReference垃圾回收触发时机以及使用场景

java 中对象引用一般引用分为四种情况 强引用 即我们平常创建的对象 Object obj new Object() 垃圾回收触发时机 在没设置 jvm 参数 -XX:PretenureSizeThreshold 和 -XX:MaxTenuringThreshold 的情况下 -XX:PretenureSizeThreshold 的值为 0,即未设置大对象直接…

三巨头对决:深入了解pnpm、yarn与npm

欢迎来到我的博客,代码的世界里,每一行都是一个故事 三巨头对决:深入了解pnpm、yarn与npm 前言包管理器简介npm(Node Package Manager):Yarn:pnpm(Performant Npm)&#…

基于Mapify的在线艺术地图设计

地图是传递空间信息的有效载体,更加美观、生动的地图产品也是我们追求目标。 那么,我们如何才能制出如下图所示这样一幅艺术性较高的地图呢?今天我们来一探究竟吧! 按照惯例,现将网址给出: https://www.m…

SpringBoot知识

1、Spring和SpringBoot对比 2、版本调整 (1)先排除是否是JDK与SpringBoot的版本不一致导致的:如JDK1.8和SpringBoot3.1.5冲突; (2)调整编译版本 (3)调整maven的jdk (4&…

Vscode运行调试文件

文章目录 vscode调试运行流程vscode 执行报错settings.json成功截图 vscode调试运行流程 vscode左侧菜单栏点击运行调试icon,点击菜单右侧栏运行和调试按钮,选择node调试器,js文件行数左边点击添加红色断点,运行当前文件 vscode…

【docker实战】01 Linux上docker的安装

Docker CE是免费的Docker产品的新名称,Docker CE包含了完整的Docker平台,非常适合开发人员和运维团队构建容器APP。 Ubuntu 14.04/16.04(使用 apt-get 进行安装) # step 1: 安装必要的一些系统工具 sudo apt-get update sudo ap…

湘潭大学-2023年下学期-c语言-作业0x0a-综合1

A 求最小公倍数 #include<stdio.h>int gcd(int a,int b) {return b>0?gcd(b,a%b):a; }int main() {int a,b;while(~scanf("%d%d",&a,&b)){if(a0&&b0) break;printf("%d\n",a*b/gcd(a,b));}return 0; }记住最大公约数的函数&…

如何编写一个javaAgent jar工具包超详细教程

介绍 Java Agent技术 Java Agent技术是JDK提供的用来编写Java工具的技术&#xff0c;使用这种技术生成一种特殊的jar包&#xff0c;这种jar包可以让Java程序 运行其中的代码。 Java Agent技术的两种模式 Java Agent技术实现了让Java程序执行独立的Java Agent程序中的代码…

【机组期末速成】CPU的结构与功能|CPU结构|指令周期概述|指令流水线|中断系统

&#x1f3a5; 个人主页&#xff1a;深鱼~&#x1f525;收录专栏&#xff1a;计算机组成原理&#x1f304;欢迎 &#x1f44d;点赞✍评论⭐收藏 前言&#xff1a; 最近在备战期末考试&#xff0c;所以本专栏主要是为了备战期末计算机组成原理这门考试&#xff0c;讲的比较浅显&…

详解Keras3.0 Layer API: Dropout layer

Dropout layer 图1 标准的神经网络 图2 加了Dropout临时删除部分神经元 Dropout层的作用是在神经网络中引入正则化&#xff0c;以防止过拟合。它通过随机丢弃一部分神经元&#xff08;如图2&#xff09;的输出来减少模型对训练数据的依赖性。这样可以提高模型的泛化能力&#x…

C++初阶——基础知识(函数重载与引用)

目录 1.命名冲突 2.命名空间 3.缺省参数 4.函数重载 1.函数重载的特点包括&#xff1a; 2.函数重载的好处包括&#xff1a; 3.引用 引用的特点包括 引用的主要用途包括 引用和指针 引用 指针 类域 命名空间域 局部域 全局域 第一个关键字 命名冲突 同一个项目之间冲…

二分查找--二分查找算法(朴素二分模板)

个人主页&#xff1a;Lei宝啊 愿所有美好如期而遇 本题题目链接https://leetcode.cn/problems/binary-search/description/ 算法原理 二段性&#xff0c;我们发现这个数组可以找到某种规律将其分为两段&#xff0c;不断划分下去&#xff0c;最终可以找到target 图示 我们分…

图灵日记之java奇妙历险记--继承和多态

目录 继承概念继承语法父类成员访问子类中访问父类的成员变量子类中访问父类的成员方法 super关键字子类构造方法super和this初始化protected关键字继承方式final 关键字继承与组合 多态条件向上转型重写动态绑定&&静态绑定多态再理解向下转型多态的优缺点好处缺陷 继承…

Element|InfiniteScroll 无限滚动组件的具体使用方法

目录 InfiniteScroll 无限滚动 基本用法 详细说明 v-infinite-scroll 指令 infinite-scroll-disabled 属性 infinite-scroll-distance 属性 总结 需求背景 &#xff1a; 项目统计管理列表页面&#xff0c;数据量过多时在 IE 浏览器上面会加载异常缓慢&#xff0c;导致刚…

如何通过易舟云财务软件,查看会计账簿的明细账?

如何通过易舟云财务软件&#xff0c;查看会计账簿的明细账&#xff1f; 前言1、会计账簿2、会计明细账 财务软件操作步骤 前言 1、会计账簿 财务软件是一种用于管理和记录企业财务活动的电子工具。会计账簿是财务软件中的一个重要功能&#xff0c;用于记录和管理企业的会计信…

superset利用mysql物化视图解决不同数据授权需要写好几次中文别名的问题

背景 在使用superset时&#xff0c;给不同的人授权不同的数据&#xff0c;需要不同的数据源&#xff0c;可视化字段希望是中文&#xff0c;所以导致不同的人需要都需要去改表的字段&#xff0c;因此引入视图&#xff0c;将视图中字段名称设置为中文 原表数据 select * from …

将按键次数写入AT24C02,再读出并用1602LCD显示

#include<reg51.h> //包含单片机寄存器的头文件 #include<intrins.h> //包含_nop_()函数定义的头文件 sbit RSP2^0; //寄存器选择位&#xff0c;将RS位定义为P2.0引脚 sbit RWP2^1; //读写选择位&#xff0c;将RW位定义为P2.1引脚 s…

【面试】 Maven 的八大核心概念

Maven 的八大核心概念 在这里&#xff0c;举出这个标题&#xff0c;自然大家知道Maven是干啥的&#xff0c;就不过多进行赘述&#xff01;我们主要对于Maven的八大核心概念做一个解释补充&#xff0c;这也是我自己的一个学习历程&#xff0c;我们一起共勉&#xff01; 文章概述…

word中MathType公式编号

直接上效果图&#xff1a; 步骤如下&#xff1a; 安装MathTypeword中安装MathType选项卡。设置MathType选项卡添加分隔符插入公式&#xff0c;自动生成右编码 接下来介绍每一步。 文章目录 1. 安装MathType2. Word中安装MathType选项卡3. 配置MathType选项4. 添加分隔符5. 插…