《循环双向链表》(带哨兵位的头节点)

目录

​编辑

前言:

 关于双向循环带头链表:

 模拟实现双向循环带头链表:

1.typedef数据类型

2.打印链表

3.初始化链表:

4.创建节点

5.尾插

6.头插

7.尾删

8.头删

9.寻找节点

10.在节点前插入

11.删除指定节点

单链表和双链表的区别:

链表和顺序表的区别:

对于顺序表的优势:

顺序表的问题:

链表的优势:

链表的不足:

 

总结:


前言:

我们在上一篇blog中,对于单向链表且不带哨兵位的头节点有了初步的认识,具体内容可以参考以下blog:《单链表》的实现(不含哨兵位的单向链表)-CSDN博客

今天我们将要对于双向链表的进行模拟实现,由于代码实现起来简单,并且大部分与单链表内容相似,所以我在这里我会进行过多的赘述,我们只是来讲解双向带头链表是什么,剩下的内容将是全部的代码模拟。

 关于双向循环带头链表:

简化一点就是:

 

我们会在节点里面再多定义一个指针——prev,指向上一个节点,这样方便我们进行操作。

因为有了上一个节点的地址,我们不管在尾删还是头删都可以在不保存任何节点的情况下对该节点进行操作,因此我们就会简化一系列操作。

下面将是各个模块的详细代码:

 模拟实现双向循环带头链表:

1.typedef数据类型

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

2.打印链表

void LTPrint(LTNode* phead)
{assert(phead);printf("哨兵位<=>");LTNode* cur = phead;while (cur!=phead){printf("%d<=>", cur->data);cur = cur->next;}printf("\n");
}

3.初始化链表:

LTNode* LTInit()
{return CreatLTNode(-1);
}

4.创建节点

LTNode* CreatLTNode(LTDataType x)
{LTNode* tmp = (LTNode*)malloc(sizeof(LTNode));if (tmp == NULL){perror("CreatNode -> malloc");exit(-1);}tmp->data = x;tmp->next = tmp;tmp->prev = tmp;return tmp;
}

5.尾插

void LTPushBack(LTNode* phead, LTDataType x)
{assert(phead);LTNode* newnode = CreatLTNode(x);if (phead->next == phead){phead->next = newnode;phead->prev = newnode;newnode->next = phead;newnode->prev = phead;}else{newnode->next = phead;phead->prev->next = newnode;newnode->prev = phead->prev;phead->prev = newnode;}
}

6.头插

void LTPushFront(LTNode* phead, LTDataType x)
{assert(phead);LTNode* newnode = CreatLTNode(x);phead->next = newnode;phead->prev = newnode;newnode->next = phead;newnode->prev = phead;
}

7.尾删

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

8.头删

void LTPopFront(LTNode* phead)
{assert(phead);assert(phead->next != phead);LTNode* tmp = phead->next;phead->next = tmp->next;tmp->next->prev = phead;free(tmp);tmp = NULL;
}

9.寻找节点

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

10.在节点前插入

void ListInsert(LTNode* phead, LTNode* pos, LTDataType x)
{assert(pos);LTNode* newnode = CreatLTNode(x);newnode->prev = pos->prev;pos->prev->next = newnode;pos->prev = newnode;newnode->next = pos;pos->prev = newnode;
}

11.删除指定节点

void ListErase(LTNode* phead, LTNode* pos)
{assert(phead);pos->prev = pos->next;pos->next->prev = pos->prev;free(pos);
}

单链表和双链表的区别:

对于单链表我们想要进行找尾操作,十分困难,时间复杂度为O(N)

而在双向链表中,我们想要进行找尾操作则直接利用phead->prev就可以找到尾结点,时间复杂对为O(1)。

在我们以后的面试中,如果我们的面试官对我们提出,如何在10min之内创建一个链表。

这时候我们就可以信心满满的写出双线带头循环链表,并且只需要写出ListErase 与 ListInsert即可,因为这两个函数不需要进行分类讨论。

链表和顺序表的区别:

学习了链表与顺序表,这两种均属于线性表的产物,那么我们该如何选择呢?

对于顺序表的优势:

1.支持下标的随机访问。

2.CPU高速缓存命中率较高

顺序表的问题:

1.头部或中间插入删除效率低,需要挪动数据O(N)

2.空间不够需要扩容,扩容一定要消耗,且可能存在一定的空间浪费。

3.只适合尾插尾删。

链表的优势:

1.任意位置插入删除都是O(1)。

2.按需申请释放,合理利用空间,不存在浪费

链表的不足:

  1. 随机访问性差:链表中的元素并不存储在连续的内存位置上,因此要访问链表中的任意元素需要遍历整个链表,时间复杂度为O(n)。

  2. 存储空间浪费:链表中每个节点需要额外的一个指针来指向下一个节点,这样会使链表中存储的数据量比较少,占用的存储空间较大。

  3. 插入和删除操作的效率较高:虽然插入和删除操作都是链表的优点,但是在处理大量数据时,频繁的插入和删除操作会导致链表不断地重新分配内存空间,影响性能。

  4. 不支持随机访问:链表的遍历方式只能是从头节点开始,依次访问每个节点,不能直接访问某个节点的位置。

  5. 不利于缓存:由于链表中的元素在内存中的存储位置是随机的,所以在对链表进行遍历时,缓存命中率较低,效率较差

 

总结:

以上就是我们的双向带头链表的实习,以及对于顺序表和链表之间的区别,学习完后下来可以及时整理整理,在之后我们会对顺序表和链表进行综合运用,我们将在后面实现《栈》《队列》的模拟操作,也会对于相应的题目进行分析。

记住

“坐而言不如起而行”

Action speak louder than words!

 

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

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

相关文章

解析:什么是生成式AI?与其他类型的AI有何不同?

原创 | 文 BFT机器人 快速浏览一下头条新闻&#xff0c;你会发现生成式AI似乎无处不在。事实上&#xff0c;一些新闻标题甚至可能是通过生成式AI编写的&#xff0c;例如OpenAI旗下的ChatGPT&#xff0c;这个聊天机器人已经展现出了生成看起来像人类所写文本的惊人能力。 当人们…

maptalks三维地图网址

三维 地址: http://examples.maptalks.com/examples/cn/gltf/gltf-marker/shader

Redis实现延时队列-工具类

Redis实现延时队列-工具类 RedisDelayQueueUtil第一种方式:优点:缺点:第二种方式:优点:缺点:总结:RedisDelayQueueUtil 依赖: <dependency><groupId>org.springframework.boot

简朴博客系统测试报告

文章目录 一. 项目简介二. 测试概要三. 测试环境四. 测试执行概况及功能测试1. 手工测试1.1 手动测试用例编写1.2 执行的部分测试用例 2. 自动化测试Selenium2.1 编写测试用例2.2 自动化测试代码 3. 测试结果 五. 发现的问题 一. 项目简介 简朴博客系统是采用前后端分离的方式…

《Effective C++》条款17

以独立语句将newed对象置入智能指针 class A {...}; int g() {...} int f(shared_ptr<A> a,g()) {...} int main() {f(shared_ptr<A> (new A), g()); } 假如你想通过主函数里的语句进行调用f函数。虽然看上去没有什么问题&#xff0c;但是实际上可能会造成内存泄漏…

【QML】警告Name is declared more than once

1. 问题&#xff1a; qml函数中的不同块中定义同名变量&#xff0c;报警&#xff1a;Name is declared more than once 举例&#xff1a; function test(a){if(a "1"){var re 1;console.log(re);}else{var re 2; //这里会报警&#xff1a;Name is declared mor…

asp.net core mvc 之 依赖注入

一、视图中使用依赖注入 1、core目录下添加 LogHelperService.cs 类 public class LogHelperService{public void Add(){}public string Read(){return "日志读取";}} 2、Startup.cs 文件中 注入依赖注入 3、Views目录中 _ViewImports.cshtml 添加引用 4、视图使用…

Scala---样例类+隐式转换

样例类(case classes) 1、概念理解 使用了case关键字的类定义就是样例类(case classes)&#xff0c;样例类是种特殊的类。实现了类构造参数的getter方法&#xff08;构造参数默认被声明为val&#xff09;&#xff0c;当构造参数是声明为var类型的&#xff0c;它将帮你实现set…

Python每日一练@前言

Python每日一练前言 导读 人生苦短&#xff0c;我用Python 大家好&#xff0c;我是鹅不糊涂 欢迎大家来到Python每日一练 好处 加强编程能力: 每日一练可以帮助提升编程技能&#xff0c;通过解决各种编程问题和挑战&#xff0c;你能够不断锻炼自己的逻辑思维和解决问题的能力…

顺序表(数据结构与算法)

✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅ ✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨ &#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1…

2023.11.17 hadoop之HDFS进阶

目录 HDFS的机制 元数据简介 元数据存储流程:namenode 生成了多个edits文件和一个fsimage文件 edits和fsimage文件 SecondaryNameNode辅助NameNode的方式: HDFS的存储原理 写入数据原理: 发送写入请求,获取主节点同意,开始写入,写入完成 读取数据原理:发送读取请求,获取…

vim——“Linux”

各位CSDN的uu们好呀&#xff0c;今天&#xff0c;小雅兰的内容是Linux的开发工具——vim。下面&#xff0c;我们一起进入Linux的世界吧&#xff01;&#xff01;&#xff01; Linux编辑器-vim使用 vim的基本概念 vim的基本操作 vim正常模式命令集 vim末行模式命令集 vim操…

【Linux网络】从原理到实操,感受PXE无人值守自动化高效批量网络安装系统

一、PXE网络批量装机的介绍 1、常见的三种系统安装方式 2、回顾系统安装的过程&#xff0c;了解系统安装的必要条件 3、什么是pxe 4、搭建pxe的原理 5、Linux的光盘镜像中的isolinux中的相关文件学习 二、关于实现PXE无人值守装机的四大文件与五个软件的对应关系详解 5个…

强缓存和弱缓存

强缓存和弱缓存是Web开发中常用的两种缓存机制。 强缓存&#xff08;Strong Cache&#xff09; 强缓存是指在浏览器发送请求前&#xff0c;先检查本地缓存中是否存在可用的资源副本。如果存在&#xff0c;并且该资源没有过期&#xff0c;服务器将返回一个特定的响应头&#xff…

使用 Redis BitMap 实现签到与查询历史签到以及签到统计功能(SpringBoot环境)

目录 一、前言二、Redis BitMap 位图原理2.1、BitMap 能解决什么2.2、BitMap 存储空间计算2.3、BitMap 存在问题 三、Redis BitMap 操作基本语法和原生实现签到3.1、基本语法3.2、Redis BitMap 实现签到操作指令 四、SpringBoot 使用 Redis BitMap 实现签到与统计功能4.1、代码…

python之 flask 框架(2)项目拆分的 执行逻辑

项目的结构图 app.py # 导入__init__.py 比较特殊 from APP import create_appapp create_app() if __name__ __main__:app.run(debugTrue)init.py # __inti__.py # 初始化文件&#xff0c;创建Flask应用 from flask import Flask from .views import bluedef create_ap…

PyTorch 实战之水果分类

当我们试图提高神经网络的准确性时&#xff0c;经常会遇到过拟合训练数据的情况。当我们运行测试数据的模型时&#xff0c;这会导致一个糟糕的预测。因此&#xff0c;我采取了一个数据集&#xff0c;并应用这些技术&#xff0c;不仅提高准确性&#xff0c;而且还处理过拟合的问…

【SA8295P 源码分析】127 - 摄像头 GMSL1、GMSL2 加串-解串器 常用寄存器配置整理(正在更新中......)

【SA8295P 源码分析】127 - 摄像头 GMSL1、GMSL2 加串-解串器 常用寄存器配置整理 一、MAX96712 解串器基础配置1.1 配置链路 GMSL 协议 及 传输速率1.1.1 配置GMSL1、GMSL2协议 及 配置使能 Link A/B/C/D [0x6]:GMSL2_A、LINK_EN_A1.1.2 配置GMSL2 传输速率 3 Gbps / 6 Gbps …

基于IDEA 进行Maven依赖管理

1. 依赖管理概念 Maven 依赖管理是 Maven 软件中最重要的功能之一。Maven 的依赖管理能够帮助开发人员自动解决软件包依赖问题&#xff0c;使得开发人员能够轻松地将其他开发人员开发的模块或第三方框架集成到自己的应用程序或模块中&#xff0c;避免出现版本冲突和依赖缺失等…

一个用于操作Excel文件的.NET开源库

推荐一个高性能、跨平台的操作Excel文件的.NET开源库。 01 项目简介 ClosedXML是一个.NET第三方开源库&#xff0c;支持读取、操作和写入Excel 2007 (.xlsx&#xff0c; .xlsm)文件&#xff0c;是基于OpenXML封装的&#xff0c;让开发人员无需了解OpenXML API底层API&#xf…