双向带头循环链表

在这里插入图片描述
今天我们在来学一个链表的内容,就是我们的双向带头循环链表,听名字就是一个很牛的链表,那我们今天就来把它的每个接口一个一个来实现。首先我们来看一下它的物理结构是一个什么样子的。

在这里插入图片描述

我们通过这个图可以很清楚的看到我们的前一个节点都是指向我们后一个节点的,但是同时我们也可以看到我们的后一个节点是指向前一个节点的,这样就很高效的帮助我们去实现一些随机插入,我们如果在仔细看这个链表的时候,我们可以发现我们的head指针,我们可以叫他们是哨兵位的节点,他是指向我们的尾节点,那我们这里就可以很快找到我们尾,并对它进行一些操作,这样也就能满足我们之前链表每次找尾节点的时候都需要遍历一次链表这样的时间复杂度就是O(N),现在直接提升成O(1)。

那我们对它结构体的定义就是下面这些代码。

typedef int LTDataType;
typedef struct ListNode
{LTDataType _data;struct ListNode* _next;struct ListNode* _prev;
}LT;

那我们第一个需要对它实现的接口函数就是初始化,这样初始化的作用就是我们需要创建出第一个节点,也就是我们平常口中说的哨兵位。

LT* LTInit()
{LT* plist = (LT*)malloc(sizeof(LT));if (plist == NULL){perror("malloc fail\n");exit(-1);}plist->next = plist;plist->prev = plist;return plist;
}

这个就是我们的初始化,我们的初始化在这里是创造出一个哨兵位的头节点,然后让它指向自己,这是为了后面的插入和删除更加方便,我们这里有一个和之前不太一样的是我们这里用了返回值,如果没有返回值会产生两个问题,一个最重要的是我们记录malloc这个节点的地址是个局部变量,malloc如果不释放是会存在内存泄漏的,所以我们这里用了返回值·,还有就是避免了二级指针,如果我们一开始定义一个指针,我们进行初始化对我们产生的影响就是我们无法改变这个指针的指向,我们这里就得传一个二级指针,所以这里用了返回值是最好的办法。

下面就是我们马上来写一个尾插的函数,尾插的函数我们之前单链表是需要找尾的,但是我们这里就可以直接把O(N)的时间复杂度变成O(1)因为我们双向链表的定义就是我们的头指针得指向我们的尾,这样就能构成循环。

那我们的push是要创造新的节点出来的,这里我们需要再写一个创造节点的函数出来,这样才能保证我们push的实现。


LT* CreateNode(LTDataType x)
{LT* newnode = (LT*)malloc(sizeof(LT));if (newnode == NULL){perror("malloc fail\n");exit(-1);}newnode->data = x;return newnode;
}void LTPushBack(LT* plist, LTDataType x)
{assert(plist);LT* newnode = CreateNode(x);/*newnode->prev = plist->prev;plist->next = newnode;newnode->next = plist;plist->prev = newnode;*///错误的,有指针进行改变了,导致链接不上了。LT* tail = plist->prev;tail->next = newnode;newnode->prev = tail;newnode->next = plist;plist->prev = newnode;}

这里也给出了一个错误的方法,因为小编一开始就是这样写的,结果发现最后双向循环链表事链接不上的,尾插的实现也就很简单,只要找到为节点然后进行保存再进行链接,保证还是一个双向循环链表就可以了。

那我们再把后面的接口函数也补上。

LT* CreateNode(LTDataType x);void LTPrint(LT* plist);void LTPushFront(LT* plist, LTDataType x);void LTPopFront(LT* plist);void LTPopBack(LT* plist);LT* LTFind(LT* plist, LTDataType x);void LTInsert(LT* plist, LT* pos, LTDataType x);void LTErase(LT* plist, LT* pos);void LTDstory(LT* plist);

其实这些接口的思路实现就和我们的单链表是差不多的,这里也就不过多的讲解,主要是一些注意的事项进行讲解。

下面再来实现打印函数,这里打印我们不能以cur为空来进行判断,因为这样就会进行死循环的打印,所以我们这里要可将cur写成head的next,判断条件就是如果cur的next是head,打印条件判断就结束了,那我们再来实现一下。

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

下一个就是头插,双向循环链表带头的好处也就出来了,我们不用怕是不是空,因为不管怎么样都是有一个哨兵位的,所以只要大胆的插入就可以了。

void LTPushFront(LT* plist, LTDataType x)
{assert(plist);LT* newnode = CreateNode(x);newnode->next = plist->next;plist->next->prev = newnode;plist->next = newnode;newnode->prev = plist;
}

下面就是pop环节,这个时候就是需要注意一些了,没有节点就不能pop,但是下面的代码是针对所以情况,哪怕你只有一个节点也是可以实现的,我们来看看吧

void LTPopFront(LT* plist)
{assert(plist);LT* first = plist->next;LT* second = plist->next->next;free(first);first = NULL;plist->next = second;second->prev = plist;
}void LTPopBack(LT* plist)
{assert(plist);assert(plist->next != plist);LT* tail = plist->prev;LT* prev = tail->prev;free(tail);tail = NULL;prev->next = plist;plist->prev = prev;}

尾删和头删都可以直接进行,而且都适用,如果画图仔细地话,当只要一个节点地时候,second就是我们创建地哨兵位。

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

FInd函数也是特别简单地实现,我们遍历一遍就行,而且cur应该是要指向我们哨兵位后面的一个节点。

那我们再来实现在pos位置前插入和pos位置进行删除的代码,双向循环带头链表就是可以这样实现的。这里我的find要和这些一起用,才能大达到我们pos的效果

void LTInsert(LT* plist, LT* pos, LTDataType x)
{assert(plist);assert(pos);LT* newnode = CreateNode(x);LT* prev = pos->prev;prev->next = newnode;newnode->prev = prev;newnode->next = pos;pos->prev = newnode;
}void LTErase(LT* plist, LT* pos)
{assert(plist);assert(pos);assert(plist->next != plist);LT* prev = pos->prev;LT* next = pos->next;free(pos);prev->next = next;next->prev = prev;
}

下面再来实现一下destory代码就算是全部欧克

void LTDstory(LT* plist)
{assert(plist);LT* cur = plist->next;while (cur->next != plist){LT* next = cur->next;free(cur);cur = next;}
}

那我们双向链表就这样简单的实现,完整代码也发下面了,这个没有过的讲解时因为很多和单链表相似,如果实现不会看看代码就是能看懂的,带头双向循环链表的优势就是看起来好像很复杂,但是用起来就是爽的特点
List.h

#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>typedef int LTDataType;
typedef struct ListNode
{LTDataType data;struct ListNode* next;struct ListNode* prev;
}LT;LT* LTInit();void LTPushBack(LT* plist, LTDataType x);LT* CreateNode(LTDataType x);void LTPrint(LT* plist);void LTPushFront(LT* plist, LTDataType x);void LTPopFront(LT* plist);void LTPopBack(LT* plist);LT* LTFind(LT* plist, LTDataType x);void LTInsert(LT* plist, LT* pos, LTDataType x);void LTErase(LT* plist, LT* pos);void LTDstory(LT* plist);

list.c

#define _CRT_SECURE_NO_WARNINGS 1#include"SList.h"LT* LTInit()
{LT* plist = (LT*)malloc(sizeof(LT));if (plist == NULL){perror("malloc fail\n");exit(-1);}plist->next = plist;plist->prev = plist;return plist;
}LT* CreateNode(LTDataType x)
{LT* newnode = (LT*)malloc(sizeof(LT));if (newnode == NULL){perror("malloc fail\n");exit(-1);}newnode->data = x;return newnode;
}void LTPushBack(LT* plist, LTDataType x)
{assert(plist);LT* newnode = CreateNode(x);/*newnode->prev = plist->prev;plist->next = newnode;newnode->next = plist;plist->prev = newnode;*///错误的,有指针进行改变了,导致链接不上了。LT* tail = plist->prev;tail->next = newnode;newnode->prev = tail;newnode->next = plist;plist->prev = newnode;}void LTPrint(LT* plist)
{assert(plist);LT* cur = plist->next;printf("哨兵位<=>");while (cur != plist){printf("%d<=>", cur->data);cur = cur->next;}printf("\n");
}void LTPushFront(LT* plist, LTDataType x)
{assert(plist);LT* newnode = CreateNode(x);newnode->next = plist->next;plist->next->prev = newnode;plist->next = newnode;newnode->prev = plist;
}void LTPopFront(LT* plist)
{assert(plist);LT* first = plist->next;LT* second = plist->next->next;free(first);first = NULL;plist->next = second;second->prev = plist;
}void LTPopBack(LT* plist)
{assert(plist);assert(plist->next != plist);LT* tail = plist->prev;LT* prev = tail->prev;free(tail);tail = NULL;prev->next = plist;plist->prev = prev;}LT* LTFind(LT* plist, LTDataType x)
{assert(plist);LT* cur = plist->next;while (cur->next != plist){if (cur->data == x){return cur;}cur = cur->next;}return NULL;
}void LTInsert(LT* plist, LT* pos, LTDataType x)
{assert(plist);assert(pos);LT* newnode = CreateNode(x);LT* prev = pos->prev;prev->next = newnode;newnode->prev = prev;newnode->next = pos;pos->prev = newnode;
}void LTErase(LT* plist, LT* pos)
{assert(plist);assert(pos);assert(plist->next != plist);LT* prev = pos->prev;LT* next = pos->next;free(pos);prev->next = next;next->prev = prev;
}void LTDstory(LT* plist)
{assert(plist);LT* cur = plist->next;while (cur->next != plist){LT* next = cur->next;free(cur);cur = next;}
}

那我们今天的分享也就到这里结束了,我们下次再见,今天连写两篇,感觉都是很水的文章,主要是最近要期末考试了,我还啥也不会。

在这里插入图片描述

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

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

相关文章

记一次线上问题引发的对 Mysql 锁机制分析

背景 最近双十一开门红期间组内出现了一次因 Mysql 死锁导致的线上问题&#xff0c;当时从监控可以看到数据库活跃连接数飙升&#xff0c;导致应用层数据库连接池被打满&#xff0c;后续所有请求都因获取不到连接而失败 整体业务代码精简逻辑如下&#xff1a; Transaction p…

机器学习线性代数知识补充

线性代数知识补充 正交矩阵与正交变换方阵特征值与特征向量相似矩阵对角化二次型正定二次型 正交矩阵与正交变换 方阵特征值与特征向量 相似矩阵 对角化 二次型 正定二次型

【C++】类与对象 I

类与对象 I &#xff1a; 前言&#xff1a;&#xff08;C&#xff09;面向过程 和&#xff08;C&#xff09;面向对象 初步认识前言&#xff1a;类的引入一、类的介绍二、类的定义&#xff08;一&#xff09;class 语法&#xff08;二&#xff09;类的两种定义方式&#xff1a;…

YOLO目标检测——番茄数据集下载分享【含对应voc、coco和yolo三种格式标签】

实际项目应用&#xff1a;番茄检测数据集说明&#xff1a;番茄目标检测数据集&#xff0c;真实场景的高质量图片数据&#xff0c;数据场景丰富标签说明&#xff1a;使用lableimg标注软件标注&#xff0c;标注框质量高&#xff0c;含voc(xml)、coco(json)和yolo(txt)三种格式标签…

Interactive Analysis of CNN Robustness

Interactive Analysis of CNN Robustness----《CNN鲁棒性的交互分析》 摘要 虽然卷积神经网络&#xff08;CNN&#xff09;作为图像相关任务的最先进模型被广泛采用&#xff0c;但它们的预测往往对小的输入扰动高度敏感&#xff0c;而人类视觉对此具有鲁棒性。本文介绍了 Pert…

数据结构上机实验——图的实现(以无向邻接表为例)、图的深度优先搜索(DFS)、图的广度优先搜索(BFS)

文章目录 数据结构上机实验1.要求2.图的实现&#xff08;以无向邻接表为例&#xff09;2.1创建图2.1.1定义图的顶点、边及类定义2.1.2创建无向图和查找2.1.3插入边2.1.4打印函数 2.2图的深度优先搜索&#xff08;DFS&#xff09;2.3图的广度优先搜索&#xff08;BFS&#xff09…

代码随想录算法训练营第五十天|123. 买卖股票的最佳时机 III、188. 买卖股票的最佳时机 IV

第九章 动态规划part11 123. 买卖股票的最佳时机 III 给定一个数组&#xff0c;它的第 i 个元素是一支给定的股票在第 i 天的价格。 设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。 注意&#xff1a;你不能同时参与多笔交易&#xff08;你必须在再次…

BES2700H开发不完全手册

BES2700H开发不完全手册 是否需要申请加入数字音频系统研究开发交流答疑群(课题组)&#xff1f;可加我微信hezkz17, 本群提供音频技术答疑服务&#xff0c;群赠送语音信号处理降噪算法&#xff0c;ANC AEC ENC EQ BF BES蓝牙耳机音频资料 1 成功编译 2 代码 3 开放文档

高通发布骁龙X Elite Oryon CPU /GitHub出现一款开源项目,让用户“拥有”更大的GPU内存|魔法半周报

我有魔法✨为你劈开信息大海❗ 高效获取AIGC的热门事件&#x1f525;&#xff0c;更新AIGC的最新动态&#xff0c;生成相应的魔法简报&#xff0c;节省阅读时间&#x1f47b; &#x1f525;资讯预览 高通发布骁龙X Elite Oryon CPU&#xff0c;性能超越苹果和英特尔&#xff0…

动手学深度学习——序列模型

序列模型 1. 统计工具1.1 自回归模型1.2 马尔可夫模型 2. 训练3. 预测4. 小结 序列模型是一类机器学习模型&#xff0c;用于处理具有时序关系的数据。这些模型被广泛应用于自然语言处理、音频处理、时间序列分析等领域。 以下是几种常见的序列模型&#xff1a; 隐马尔可夫模型…

ubuntu18.04配置Java环境与安装RCS库

一、安装包 安装包 二、JAVA环境 java无需安装&#xff0c;只需要下载解压&#xff0c;然后配置正确的路径到环境变量种即可使用。 1.创建文件JAVA mkdir JAVA 2.将安装包复制到该文件夹下&#xff0c;并解压缩 tar -zxvf tar -zxvf jdk1.8.0_191.tar.gz 3.在home路径下…

Nexus的Maven私有仓库搭建

Nexus的maven私有仓库搭建 一、了解 maven仓库设置 默认设置 其中&#xff1a; maven-central: 预定义的代理Maven Central仓库&#xff0c;它包含了大量的开源Java依赖包。maven-public: 存储库是一个组合存储库&#xff0c;它包含了maven-releases和maven-snapshots存储库…