C语言数据结构——详细讲解 双链表

从单链表到双链表:数据结构的演进与优化

  • 前言
  • 一、单链表回顾
  • 二、单链表的局限性
  • 三、什么是双链表
  • 四、双链表的优势
    • 1.双向遍历
    • 2.不带头双链表的用途
    • 3.带头双链表的用途
  • 五、双链表的操作
    • 双链表的插入操作
      • (一)双链表的尾插操作
      • (二)双链表的头插操作
      • (三)双链表在指定位置之后插入数据
    • 双链表的删除操作
      • (一)双链表的尾删操作
      • (二)双链表的头删操作
      • (三)双链表删除指定位置数据


前言

         在数据结构的广袤天地里,链表作为一种重要的线性数据结构,以其独特的存储方式和灵活的操作特性,在众多编程场景中发挥着关键作用。今天,我们就来深入探讨一下从单链表双链表的发展历程,看看双链表是如何在单链表的基础上应运而生,并解决了单链表所面临的一些问题


一、单链表回顾

  • 首先,我们来回顾一下单链表的基本结构。单链表是由一系列节点组成的数据结构,每个节点包含两部分数据域指针域数据域用于存储具体的数据指针域则存储指向下一个节点的指针,通过这样的指针链接,各个节点依次相连,形成了一条链表。
  • 单链表不带头单向不循环,是链表结构中较为基础的一种形式,与带头节点的单链表不同不带头节点的单链表在进行操作时,第一个节点就直接存储了实际的数据,没有专门设置一个额外的头节点来简化某些操作逻辑。

以下是带头单链表和不带头单链表的图片

在这里插入图片描述
在这里插入图片描述

以上这些图片均表示单链表

  • 例如,我们有一个简单的单链表存储整数数据,节点结构可以定义如下
typedef struct ListNode 
{int data;struct ListNode *next;
} ListNode;
  • 单链表很多场景下都非常有用,它能够方便地进行动态内存分配实现数据的灵活存储操作,比如插入删除节点等操作相对数组来说更加灵活,不需要移动大量元素

二、单链表的局限性

  • 然而,单链表也存在一些局限性:
  • 单向遍历

单链表的指针只能指向一个方向,也就是只能从表头顺着指针依次访问到表尾的节点

  • 删除操作的不便:

当我们想要删除单链表中的某个节点时,如果只知道要删除节点的指针(而不是它的前驱节点指针),操作起来会比较麻烦,,往往需要从头开始遍历链表去找到前驱节点,这同样会影响操作的效率。

为了克服单链表的这些局限性,我们就引入了双链表

在这里插入图片描述

三、什么是双链表

  • 双链表的每个节点除了包含数据域和指向下一个节点的指针域外还增加了一个指向前一个节点的指针域。也就是说,双链表的节点结构可以定义如下:

在这里插入图片描述

typedef struct DoublyListNode 
{int data;struct DoublyListNode *prev;struct DoublyListNode *next;
} DoublyListNode;
  • 在这个结构中,**prev 指针指向当前节点的前一个节点,next 指针指向当前节点的下一个节点。**这样一来,双链表就具备了双向遍历的能力。

双链表可以分为带头节点和不带头结点

  • 不带头节点的双链表
    在这里插入图片描述
  • 结构特点:

1.链表的第一个节点就直接存储实际的数据元素,不存在一个专门作为 “头” 的额外节点。
2.每个节点除了数据域外,有两个指针域,一个指向前驱节点(prev 指针),一个指向后继节点(next 指针)

  • 带头结点的双链表
    在这里插入图片描述

  • 结构特点:

1.有一个专门的头节点,这个头节点的数据域一般不存储实际的数据(当然也可以根据具体需求赋予特殊含义,但通常为空数据域),主要作用是方便链表的各种操作。
2.头节点同样有两个指针域,prev 指针一般指向 NULL(因为它是表头,前面没有节点了),next 指针指向链表中的第一个实际存储数据的节点。

四、双链表的优势

1.双向遍历

从表头到表尾遍历:和单链表类似,我们可以通过每个节点的 next 指针依次访问后续节点,从而实现从表头到表尾的遍历。

  • 删除操作的便利性

在双链表中删除一个节点就变得更加容易了。假设我们要删除节点 p,我们可以直接通过 p 的 prev 和 next 指针来完成删除操作,而不需要像单链表那样去专门寻找它的前驱节点

总结一下带头和不带头有什么用

2.不带头双链表的用途

  • 节省内存空间:

每一个节点都实实在在地用于存储数据及相关指针,不存在一个仅为了方便操作而占据空间但通常不存储实际有效数据的头节点。

  • 体现数据原始性

3.带头双链表的用途

  • 简化操作逻辑:
  • 插入 无论是头插、尾插还是指定位置插入,由于有头节点的存在,不需要针对链表为空的情况单独设计特殊的处理逻辑。

  • 删除 进行头删、尾删或指定位置删除时,操作逻辑相对统一,不用特别考虑链表为空时删除操作的特殊变化。

  • 遍历 遍历链表时,从头节点的下一个节点开始顺着 next 指针向后遍历(正向遍历)以及从链表末尾顺着 prev 指针向前遍历(反向遍历)的逻辑较为清晰、简单,不用像不带头双链表那样,正向遍历要从第一个实际存储数据的节点开始,反向遍历要先找到末尾节点。

五、双链表的操作

双链表的插入操作

(一)双链表的尾插操作

void LTTPushFront(LTNode* head, int x)
{assert(head);LTNode* newnode = new LTNode;newnode->data = x;newnode->next = head;head = newnode;// 打印链表printList(head);
}
  • head 为双链表的头结点
  • x为要插入的数值

插入节点的步骤

  • LTNode* newnode = new LTNode创建新节点
  • newnode->data = x初始化新节点的数据
  • newnode->next = head
  • 这行代码将新节点的next指针指向当前的头节点head。这样,新节点就指向了原来的第一个节点
  • head = newnode
  • 这行代码将头节点head更新为新节点newnode。这样,新节点就成为了链表的新头节点。

(二)双链表的头插操作

// 头插操作
void LTPushFront(LTNode* head, int x) 
{assert(head);LTNode* newnode = buyNode(x);newnode->next = head->next;newnode->prev = head;head->next->prev = newnode;head->next = newnode;
}
  • newnode->next = head->next;
  • newnode->prev = head;
  • 第一行代码将新节点newnode的next指针指向当前头节点head的下一个节点(即原来链表中的第一个节点)。
  • 第二行代码将新节点newnode的prev指针指向头节点head
  • head->next->prev = newnode;
  • 将原来链表中第一个节点(即head->next)的prev指针指向新节点newnode。这一步是为了让原来的第一个节点知道它的前一个节点变成了新节点
  • head->next = newnode;
  • 将头节点head的next指针指向新节点newnode。这一步是为了让头节点指向新插入的节点,从而使新节点成为链表中的第一个节点

(三)双链表在指定位置之后插入数据

// 在pos位置之后插入数据
void LTInsert(LTNode* pos, int x) {assert(pos);LTNode* newnode = buyNode(x);newnode->next = pos->next;newnode->prev = pos;if (pos->next) {pos->next->prev = newnode;}pos->next = newnode;
}
  • newnode->next = pos->next;:
  • 将新节点newnode的next指针指向pos节点的下一个节点。
  • newnode->prev = pos;
  • 将新节点newnode的prev指针指向pos节点
  • if (pos->next) { pos->next->prev = newnode; }:
  • 如果pos节点有下一个节点(即pos->next不为NULL),那么将pos的下一个节点的prev指针指向新节点newnode。
  • 这一步是为了保持双链表的双向连接关系
  • 最后将pos节点的next指针指向新节点newnode,完成新节点在pos节点之后的插入操作

双链表的删除操作

(一)双链表的尾删操作

// 尾删
void LTPopBack(LTNode* head) 
{assert(!LTEmpty(head));LTNode* del = head->prev;LTNode* prev = del->prev;prev->next = head;head->prev = prev;delete del;
}
  • LTNode* del = head->prev;
  • 双链表通常有一个头节点,尾节点是头节点的前一个节点,所以这里head->prev就是尾节点,将其赋值给del
  • LTNode* prev = del->prev;,找到尾节点del的前一个节点prev
  • prev->next = head;,将尾节点的前一个节点的next指针指向头节点,这样就把尾节点从链表中移除了。
  • head->prev = prev;,同时更新头节点的prev指针指向新的尾节点(原来尾节点的前一个节点)

(二)双链表的头删操作

// 头删操作
void LTpopFront(LTNode* head) {assert(!LTEmpty(head));LTNode* del = head->next;head->next = del->next;if (del->next) {del->next->prev = head;}delete del;
}
  • LTNode* del = head->next;头节点的下一个节点就是要删除的第一个数据节点,将其赋值给del
  • head->next = del->next;,将头节点的next指针指向要删除节点del的下一个节点,这样就把del从链表中移除了。
  • if (del->next) { del->next->prev = head; },如果del有下一个节点(即del不是尾节点),那么将del的下一个节点的prev指针指向头节点

(三)双链表删除指定位置数据

// 删除指定位置数据
void LTEarse(LTNode* pos) {assert(pos);pos->prev->next = pos->next;if (pos->next) {pos->next->prev = pos->prev;}delete pos;
}
  • pos->prev->next = pos->next;,将pos节点的前一个节点的next指针指向pos节点的下一个节点,这样就把pos从链表中移除了。
  • if (pos->next) { pos->next->prev = pos->prev; },如果pos有下一个节点,那么将pos的下一个节点的prev指针指向pos的前一个节点
文章到这里就结束了,感谢你的阅读,喜欢的话记得三连

在这里插入图片描述

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

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

相关文章

MYSQL 表的增删改查(上)

目录 1.新增数据 2.查询数据 一般查询 去重查询 排序查询 关于NULL 条件查询 分页查询 1.新增数据 语法:insert into 表名[(字段1,字段2...)] values (值,值....); 插入一条新数据行,前面指定的列,要与后面v…

重学SpringBoot3-异步编程完全指南

更多SpringBoot3内容请关注我的专栏:《SpringBoot3》 期待您的点赞👍收藏⭐评论✍ 重学SpringBoot3-异步编程完全指南 1. 简介2. Async注解2.1 基础配置2.2 基本使用2.3 自定义线程池 3. WebFlux响应式编程3.1 依赖配置3.2 响应式Controller示例3.3 响应…

【Linux】Linux 内存管理机制

前言 Linux 的内存管理机制是一个复杂而高效的系统,旨在确保系统资源的高效利用,同时提供良好的性能和响应能力。本文主要介绍 Linux 内存管理的主要组件和机制。 虚拟内存 概念: 每个进程在 Linux 中拥有自己的虚拟地址空间,这使得进程之…

Docker pull镜像拉取失败

因为一些原因,很多镜像仓库拉取镜像失败,所以需要更换不同的镜像,这是2024/11/25测试可用的仓库。 标题1、 更换镜像仓库的地址,编辑daemon.json文件 vi /etc/docker/daemon.json标题2、然后将下面的镜像源放进去或替换掉都可以…

对智能电视直播App的恶意监控

首先我们要指出中国广电总局推出的一个政策性文件是恶意监控的始作俑者,这个广电总局的政策性文件禁止智能电视和电视盒子安装直播软件。应该说这个政策性文件是为了保护特殊利益集团,阻挠技术进步和发展的。 有那么一些电视机和电视盒子的厂商和电信运…

网安基础知识|IDS入侵检测系统|IPS入侵防御系统|堡垒机|VPN|EDR|CC防御|云安全-VDC/VPC|安全服务

网安基础知识|IDS入侵检测系统|IPS入侵防御系统|堡垒机|VPN|EDR|CC防御|云安全-VDC/VPC|安全服务 IDS入侵检测系统 Intrusion Detection System 安全检测系统,通过监控网络流量、系统日志等信息,来检测系统中的安全漏洞、异常行为和入侵行为。 分为&am…

Golang面经

一、基础 1.make与new的区别 相同点:都是给变量分配内存 不同点: 作用变量类型不同,new给string,int,bool等分配内存,make给切片,map,channel分配内存;返回类型不一样,new返回指向变量的指针,make返回变量本身;new 分配的空间被初始化为零值。make 分配空间后会进…

文件导入-使用java反射修改日期数据

文件导入时,时间类型通常不能直接导出,以下方法为批量处理类中日期类型转字符串类型。 Date/Datetime --> String(yyyy-mm-dd)Field[] declaredFields HrAviationstudentMonitorDTO.class.getDeclaredFields(); for (Field field : declaredFields) …

C语言学习 12(指针学习1)

一.内存和地址 1.内存 在讲内存和地址之前,我们想有个⽣活中的案例: 假设有⼀栋宿舍楼,把你放在楼⾥,楼上有100个房间,但是房间没有编号,你的⼀个朋友来找你玩,如果想找到你,就得挨…

VITE+VUE3+TS环境搭建

前言(与搭建项目无关): 可以安装一个node管理工具,比如nvm,这样可以顺畅的切换vue2和vue3项目,以免出现项目跑不起来的窘境。我使用的nvm,当前node 22.11.0 目录 搭建项目 添加状态管理库&…

Zookeeper选举算法与提案处理概览

共识算法(Consensus Algorithm) 共识算法即在分布式系统中节点达成共识的算法,提高系统在分布式环境下的容错性。 依据系统对故障组件的容错能力可分为: 崩溃容错协议(Crash Fault Tolerant, CFT) : 无恶意行为,如进程崩溃,只要…

ffmpeg视频滤镜:提取缩略图-framestep

滤镜描述 官网地址 > FFmpeg Filters Documentation 这个滤镜会间隔N帧抽取一帧图片&#xff0c;因此这个可以用于设置视频的缩略图。总体上这个滤镜比较简单。 滤镜使用 滤镜参数 framestep AVOptions:step <int> ..FV....... set frame st…

微服务篇-深入了解使用 RestTemplate 远程调用、Nacos 注册中心基本原理与使用、OpenFeign 的基本使用

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 认识微服务 1.1 单体架构 1.2 微服务 1.3 SpringCloud 框架 2.0 服务调用 2.1 RestTemplate 远程调用 3.0 服务注册和发现 3.1 注册中心原理 3.2 Nacos 注册中心 …

uniapp在H5使用vue-router路由返回上一页不会触发销毁函数解决方法

问题&#xff1a;uniapp在H5使用vue-router路由&#xff0c;如果在H5平台上进行页面刷新操作&#xff0c;再返回上一页&#xff0c;可能会遇到beforeDestroy、destroyed、onUnload生命周期钩子不被触发的问题。这是因为在H5中&#xff0c;页面的刷新实际上是整个应用的重新加载…

TCP/IP学习笔记

TCP\IP从实际应用的五层结构开始&#xff0c;自顶而下的去分析每一层。 TCP/IP五层架构概述 学术上面是TCP/IP四层架构&#xff0c;OSI/ISO是七层架构&#xff0c;实际中使用的是TCP/IP五层架构。 数据链路层 ICMP数据包分析 Wireshark抓包分析ICMP协议_wireshark抓ping包分析…

互联网视频推拉流EasyDSS视频直播点播平台视频转码有哪些技术特点和应用?

视频转码本质上是一个先解码再编码的过程。在转码过程中&#xff0c;原始视频码流首先被解码成原始图像数据&#xff0c;然后再根据目标编码标准、分辨率、帧率、码率等参数重新进行编码。这样&#xff0c;转换前后的码流可能遵循相同的视频编码标准&#xff0c;也可能不遵循。…

HTML 常用标签属性汇总一〈body〉标签

背景属性:包括:bgcolor,background <body background—color:black〉 背景颜色 <body background—image : url(image/bg.gif)〉 背景图片 <body background—attachment : fixed〉 固定背景 〈body background—repeat : repeat〉 重复排列—网页预设 〈b…

深入理解 Java 基本语法之运算符

&#xff08;一&#xff09;研究背景 在 Java 编程中&#xff0c;运算符是处理数据和变量的基本工具&#xff0c;掌握各种运算符的使用方法对于提高编程效率至关重要。 &#xff08;二&#xff09;研究目的 深入理解 Java 基础运算符的概念、分类和作用&#xff0c;通过具体…

CRTP mixins EBO

CRTP mixins EBO 简洁的语言概括一下就是&#xff1a; CRTP&#xff1a;子类是父类的模板参数&#xff0c;用来实现静态多态 mixins&#xff1a;原本是一种通过继承增加类功能的设计模式&#xff0c;借助CRTP的话&#xff0c;应该和上面常说的CRTP反过来了&#xff1a;父类…

Excel把其中一张工作表导出成一个新的文件

excel导出一张工作表 一个Excel表里有多个工作表&#xff0c;怎么才能导出一个工作表&#xff0c;让其生成新的Excel文件呢&#xff1f; 第一步&#xff1a;首先打开Excel表格&#xff0c;然后选择要导出的工作表的名字&#xff0c;比如“Sheet1”&#xff0c;把鼠标放到“She…