当前位置: 首页 > news >正文

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

引言

单链表存在缺陷:需要从头开始找前一个节点

解决方法:双向链表

链表的结构(8种):

1. 单向,双向

2. 带头、不带头

  • 带头即为带哨兵位的头节点,第一个节点不存储有效数据。
  • 带头节点,不需要改变传过来的指针,也就是意味着不需要传二级指针了,因为不管是头删还是尾删都不会改变头结点的位置,故不用二级指针进行传参。
  • 做好不要用头节点存链表的长度

3. 循环,非循环

之前说的链表里最后一个节点指向空指针,循环链表里最后一个结点指向第一个结点的地址

链表结构可分为单向带头循环,单向不带头循环,将以上三类进行排列组合可得2*2*2=8种链表结构

这里我就说一下带头双向循环链表吧

图示方框为头节点,不添加任何有效数据,头节点的前驱指向3的位置,3的后驱指向头节点,图示就是带头双向循环链表了

我们先来简单实现带头双向循环链表吧

带头双向循环链表的初始化

将头节点的前驱和后驱均指向自己。

typedef double LTDataType;
typedef struct ListNode
{struct ListNode* next;struct ListNode* prev;int data;
}ListNode;
ListNode* ListInit();
//创建一个结点
ListNode* BuyListNode(LTDataType x)
{ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));newnode->data = x;newnode->prev = NULL;newnode->next = NULL;return newnode;
}
初始化链表
ListNode* ListInit()
{ListNode* phead = BuyListNode(0);phead->data = phead;phead->prev = phead;return phead;
}

带头双向循环链表的尾插:

双向循环链表可直接通过头节点找到尾节点,

将尾节点的next指向新建节点,

新建节点prev指向最初尾节点,

新建节点的next的指向头节点,

头节点的prev指向新建节点。

//尾插
void ListPushBack(ListNode* phead, LTDataType x) 
{//无论链表是否为空,均可尾插ListNode* tail = phead->prev;ListNode* newnode = BuyListNode(x);newnode->prev=tail;newnode->next = phead;phead->prev = newnode;}

 带头双向链表的输出:

遍历整个链表,直到遍历到头节点(循环)

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

带头双向链表的头删:

记录第一个和第二个节点的位置,

将头节点next指向第二个节点

,第二个节点的prev指向头节点即可完成头删。

void ListPopFront(ListNode* phead, LTDataType x) 
{assert(phead);assert(phead->next!=NULL);//保证无结点时不删除ListNode* first = phead->next;ListNode* second = first->next;phead->next = second;second->prev = phead;free(first);first = NULL;
}

带头双向链表的头插:

首先要记录第一个节点的位置,以防位置被覆盖

将头节点的next指向新建节点,

新建节点的prev指向头节点,

新建节点的next指向第一个节点,

第一个节点的prev指向新建节点。

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

带头双向链表的尾删:

记录左后一个节点以及倒数第二个节点的位置。

将倒数第二个节点的next指向头节点,

头结点的prev指向倒数第二个节点,

释放掉最后一个节点。

//尾删
void ListPopBack(ListNode* phead)
{assert(phead);assert(phead->next != NULL);//不可把头结构删除ListNode* tail = phead->prev;ListNode* prev = tail->prev;prev->next = phead;phead->prev = prev;free(tail);tail = NULL;
}

带头双向循环链表的查找:

 遍历链表直到遍历至头节点,若找到要找的值,就返回该地址,遍历完还没有找到就返回NULL

//查找
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;
}

在带头双向链表的某个节点前插入新的结点:

记录插入位置的前一个位置,

新建链表的prev指向前一个链表,

前一个链表的next指向新建链表,

新建链表的next指向插入位置

,插入位置的prev指向前一个位置。

// 在pos之前插入x
void ListInsert(ListNode* pos, LTDataType x)
{assert(pos);ListNode* prev = pos->prev;ListNode* newnode = BuyListNode(x);newnode->prev = prev;prev->next = newnode;newnode->next = pos;pos->prev = newnode;
}

 删除带头双向链表的某个结点:

记录删除结点位置的前一个节点位置以及后一个节点的位置

前一个节点的next指向后一个节点,

后一个节点的prev指向前一个节点

释放要删除节点的指针

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

释放掉整个链表:

 

遍历整个链表,注意在释放节点时,要先记录下一个节点,遍历完之后,释放头节点。

//释放链表
void ListDestory(ListNode* phead)
{assert(phead);ListNode* cur = phead->next;while (cur != phead){ListNode* next = cur->next;free(cur);cur = next;}free(phead);
}

双向带头循环链表的特点,结构复杂,但操作简单

带头双向循环 -- 最优链表结构,任意位置插入删除数据空间复杂度都是0(1)。

相关文章:

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

引言 单链表存在缺陷:需要从头开始找前一个节点 解决方法:双向链表 链表的结构(8种): 1. 单向,双向 2. 带头、不带头 带头即为带哨兵位的头节点,第一个节点不存储有效数据。带头节点&#…...

最小生成树(Minimum Spanning Tree)及生成MST的几种方法

最小生成树 (Minimum Spanning Tree) 最小生成树是图论领域的一个基本概念,适用于加权连通图,其中包括若干顶点(节点)以及连接这些顶点的边(边可以有权重)。在一个加权连通图中,生成树&#xf…...

逻辑漏洞 暴力破解(DVWA靶场)与验证码安全 (pikachu靶场) 全网最详解包含代码审计

逻辑漏洞 暴力破解(DVWA靶场)与验证码安全 (pikachu靶场) 全网最详解包含代码审计 0x01 前言 在当今互联网的广袤世界中,各式交互平台层出不穷。每一个交互平台几乎都要求用户注册账号,而这些账号则成为我们在数字世界中的身份象征。账号的安全性变得至…...

io基础入门

压缩的封装 参考:https://blog.csdn.net/qq_29897369/article/details/120407125?utm_mediumdistribute.pc_relevant.none-task-blog-2defaultbaidujs_baidulandingword~default-0-120407125-blog-120163063.235v38pc_relevant_sort_base3&spm1001.2101.3001.…...

k8s ingress 无法找到端点

文章目录 ingress rule无法找到端点这个注解是什么意思呢?为何不生效呢?端点无法更新?如何确认ingressclass呢?修复端点无法发现的问题多个ingress controller 架构 ingress rule无法找到端点 在vnnox-cn集群创建ingress&#xf…...

properties转yml

目前搜索到的大部分代码都存在以下问题: 复杂结构解析丢失解析后顺序错乱 所以自己写了一个,经过不充分测试,基本满足使用。可以直接在线使用 在线地址 除了yml和properties互转之外,还可以生成代码、sql转json等,可…...

谈谈中间件设计的思路

前言 想要设计和真正理解中间件的架构理论和思想。对于开发来说需要具备三个关键的能力 1:基础通用技术的深入理解和运用2:了解和熟悉常见中间件的设计思想,且有自己的感悟,并且能按照自己的理解模仿写一写3:业务的高度理解能力…...

WT2605-24SS音频蓝牙录放语音芯片:标准蓝牙功能与多样化存储播放方式助力音频体验升级

在音频技术日新月异的今天,WT2605-24SS音频蓝牙录放语音芯片以其强大的功能和出色的性能,成为了音频市场的一颗璀璨明星。该芯片不仅具备标准音频蓝牙功能,还支持蓝牙电话本、录音功能以及多种存储和播放方式,为用户提供了更加便捷…...

openssl生成ssl证书

x509证书一般会用到三类文,key,csr,crt。 Key 是私用密钥openssl格,通常是rsa算法。 Csr 是证书请求文件,用于申请证书。在制作csr文件的时,必须使用自己的私钥来签署申,还可以设定一个密钥。…...

以太网PHY,MAC接口

本文主要介绍以太网的 MAC 和 PHY,以及之间的 MII(Media Independent Interface ,媒体独立接口)和 MII 的各种衍生版本——GMII、SGMII、RMII、RGMII等。 简介 从硬件的角度看,以太网接口电路主要由MAC(M…...

c语言中 , x++ 和 ++x的区别

一 c语言中 , x 和 x的区别 x 和 x 是 C 语言中的自增运算符,它们的区别在于它们的执行时机和返回值: 1. x (后缀自增): 先使用变量的值,然后再将变量的值加 1。这意味着,如果你在一个表达式中使用了 x,那么该表达式…...

DBeaver 社区版(免费版)下载、安装、解决驱动更新出错问题

DBeaver 社区版(免费版) DBeaver有简洁版,企业版,旗舰版,社区版(免费版)。除了社区版,其他几个版本都是需要付费的,当然相对来说,功能也要更完善些&#xff…...

景联文科技加入中国人工智能产业联盟(AIIA)数据委员会

近日,景联文科技加入中国人工智能产业联盟(AIIA)数据委员会,成为委员会成员单位。 中国人工智能产业发展联盟(简称AIIA)是在国家发改委、科技部、工信部、网信办指导下,由中国信息通信研究院等单…...

数据结构 / 结构体指针

1. 格式 struct 结构体名{数据类型 成员1;数据类型 成员2; .... };struct 结构体名 *指针变量名 2. 结构体指针指向普通变量的地址 struct CAR{char name[10];int price; };struct CAR car{"byd",160}; struct CAR *p&car; //p是指向结构体变量car的指针// p…...

P1 什么是链表 C语言简单易懂

目录 前言 01 什么是链表 02 数组的特点 03 数组的缺点 3.1 删除数组其中一个元素 3.2 数组增加某个节点 04 链表 前言 🎬 个人主页:ChenPi 🐻推荐专栏1: 《 C 》✨✨✨ 🔥 推荐专栏2: 《 Linux C应用编程(概念…...

Python实现FA萤火虫优化算法优化循环神经网络分类模型(LSTM分类算法)项目实战

说明:这是一个机器学习实战项目(附带数据代码文档视频讲解),如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 萤火虫算法(Fire-fly algorithm,FA)由剑桥大学Yang于2009年提出 , …...

Spring Task

Spring Task 是Spring框架提供的任务调度工具,可以按照约定的时间自动执行某个代码逻辑。 **定位:**定时任务框架 **作用:**定时自动执行某段Java代码 cron表达式 cron表达式其实就是一个字符串,通过cron表达式可以定义任务触…...

HttpServletRequest/Response视频笔记

学习地址:144-尚硅谷-Servlet-HttpServletRequest类的介绍_哔哩哔哩_bilibili 目录 1.HttpServletRequest 类 a.HttpServletRequest类有什么作用 b.HttpServletRequest类的常用方法 c.如何获取请求参数 d.解决post请求中文乱码问题 获取请求的参数值相关问题 …...

网上选课系统源码(Java)

JavaWebjsp网上选课系统源码 运行示意图:...

mac修改默认shell为bash

1. 打开系统偏好设置 2. 点击用户群组 3. 按住ctrl,点击用户名 4. 点击高级选项,修改登录shell 参考:在 Mac 上将 zsh 用作默认 Shell - 官方 Apple 支持 (中国)...

Vim 调用外部命令学习笔记

Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...

[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解

突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 ​安全措施依赖问题​ GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...

<6>-MySQL表的增删查改

目录 一,create(创建表) 二,retrieve(查询表) 1,select列 2,where条件 三,update(更新表) 四,delete(删除表&#xf…...

进程地址空间(比特课总结)

一、进程地址空间 1. 环境变量 1 )⽤户级环境变量与系统级环境变量 全局属性:环境变量具有全局属性,会被⼦进程继承。例如当bash启动⼦进程时,环 境变量会⾃动传递给⼦进程。 本地变量限制:本地变量只在当前进程(ba…...

树莓派超全系列教程文档--(62)使用rpicam-app通过网络流式传输视频

使用rpicam-app通过网络流式传输视频 使用 rpicam-app 通过网络流式传输视频UDPTCPRTSPlibavGStreamerRTPlibcamerasrc GStreamer 元素 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 使用 rpicam-app 通过网络流式传输视频 本节介绍来自 rpica…...

智慧工地云平台源码,基于微服务架构+Java+Spring Cloud +UniApp +MySql

智慧工地管理云平台系统,智慧工地全套源码,java版智慧工地源码,支持PC端、大屏端、移动端。 智慧工地聚焦建筑行业的市场需求,提供“平台网络终端”的整体解决方案,提供劳务管理、视频管理、智能监测、绿色施工、安全管…...

linux 错误码总结

1,错误码的概念与作用 在Linux系统中,错误码是系统调用或库函数在执行失败时返回的特定数值,用于指示具体的错误类型。这些错误码通过全局变量errno来存储和传递,errno由操作系统维护,保存最近一次发生的错误信息。值得注意的是,errno的值在每次系统调用或函数调用失败时…...

零基础在实践中学习网络安全-皮卡丘靶场(第九期-Unsafe Fileupload模块)(yakit方式)

本期内容并不是很难,相信大家会学的很愉快,当然对于有后端基础的朋友来说,本期内容更加容易了解,当然没有基础的也别担心,本期内容会详细解释有关内容 本期用到的软件:yakit(因为经过之前好多期…...

安卓基础(aar)

重新设置java21的环境,临时设置 $env:JAVA_HOME "D:\Android Studio\jbr" 查看当前环境变量 JAVA_HOME 的值 echo $env:JAVA_HOME 构建ARR文件 ./gradlew :private-lib:assembleRelease 目录是这样的: MyApp/ ├── app/ …...

华硕a豆14 Air香氛版,美学与科技的馨香融合

在快节奏的现代生活中,我们渴望一个能激发创想、愉悦感官的工作与生活伙伴,它不仅是冰冷的科技工具,更能触动我们内心深处的细腻情感。正是在这样的期许下,华硕a豆14 Air香氛版翩然而至,它以一种前所未有的方式&#x…...