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

news2024/11/20 8:27:13

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

  • 前言
  • 一、单链表回顾
  • 二、单链表的局限性
  • 三、什么是双链表
  • 四、双链表的优势
    • 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.coloradmin.cn/o/2243925.html

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

相关文章

【ArcGISPro】地理配准-影像校正

由于大部分数据安全性,以下是随意下载的图片,仅展示配置操作 地图-地理配准 添加控制点 修改控制点 可以导入、导出、添加和删除控制点 保存 关闭地理配准

ReNamer Pro 7.5 中文绿色便携专业版-文件重命名工具

前言 我们日常生活和工作中所涉及的文件数量日益增多。无论是图片、音频、视频还是各种文档,这些文件在存储、管理和分享时,都需要有一个清晰、有序的文件命名规则。然而,手动重命名大量文件不仅耗时耗力,而且容易出错&#xff0c…

PgSQL即时编译JIT | 第1期 | JIT初识

PgSQL即时编译JIT | 第1期 | JIT初识 JIT是Just-In-Time的缩写,也就是说程序在执行的时候生成可以执行的代码,然后执行它。在介绍JIT之前,需要说下两种执行方式:解释执行和编译执行。其中解释执行是通过解释器,将代码逐…

力扣-Hot100-数组【算法学习day.37】

前言 ###我做这类文档一个重要的目的还是给正在学习的大家提供方向(例如想要掌握基础用法,该刷哪些题?)我的解析也不会做的非常详细,只会提供思路和一些关键点,力扣上的大佬们的题解质量是非常非常高滴&am…

DataStream编程模型之数据源、数据转换、数据输出

Flink之DataStream数据源、数据转换、数据输出(scala) 0.前言–数据源 在进行数据转换之前,需要进行数据读取。 数据读取分为4大部分: (1)内置数据源; 又分为文件数据源; socket…

爬虫开发工具与环境搭建——使用Postman和浏览器开发者工具

第三节:使用Postman和浏览器开发者工具 在网络爬虫开发过程中,我们经常需要对HTTP请求进行测试、分析和调试。Postman和浏览器开发者工具(特别是Network面板和Console面板)是两种最常用的工具,能够帮助开发者有效地捕…

vue2侧边导航栏路由

<template><div><!-- :default-active"$route.path" 和index对应其路径 --><el-menu:default-active"active"class"el-menu-vertical-demo"background-color"#545c64"text-color"#fff"active-text-col…

时代变迁对传统机器人等方向课程的巨大撕裂

2020年之后&#xff0c;全面转型新质课程规划&#xff0c;传统课程规划全部转为经验。 农耕-代表性生产关系-封建分配制度主要生产力-人力工业-代表性生产关系-资本分配制度工业分为机械时代&#xff0c;电气时代&#xff0c;信息时代&#xff1b;主要生产力-人力转为人脑&…

JVM类加载过程-Loading

一、Class对象的生命周期 .class文件是如何加载到内存中:.class文件是ClassLoader通过IO将文件读到内存,再通过双亲委派的模式进行Loading,再Linking、以及Initializing,代码调用等一系列操作后,进行GC,组成完整的生命周期; 二、双亲委派模式(Loading的过程): 1、类…

BERT--公认的里程碑

前言 如果说&#xff0c;让我选Transformer架构的哪个模型最深入人心&#xff0c;我将毫不犹豫的选择BERT&#xff01; BERT 的意义在于&#xff0c;从大量无标记的数据集中训练得到的深度模型&#xff0c;可以限制提高各项自然语言处理任务的准确率。 BERT 在当时&#xff0…

<项目代码>YOLOv8 瞳孔识别<目标检测>

YOLOv8是一种单阶段&#xff08;one-stage&#xff09;检测算法&#xff0c;它将目标检测问题转化为一个回归问题&#xff0c;能够在一次前向传播过程中同时完成目标的分类和定位任务。相较于两阶段检测算法&#xff08;如Faster R-CNN&#xff09;&#xff0c;YOLOv8具有更高的…

每日OJ题_牛客_天使果冻_递推_C++_Java

目录 牛客_天使果冻_递推 题目解析 C代码 Java代码 牛客_天使果冻_递推 天使果冻 描述&#xff1a; 有 n 个果冻排成一排。第 i 个果冻的美味度是 ai。 天使非常喜欢吃果冻&#xff0c;但她想把最好吃的果冻留到最后收藏。天使想知道前 x个果冻中&#xff0c;美味…

果韵 2.0.1| 听歌神器,双端支持,支持下载歌曲和歌词

果韵是一款支持Windows和安卓双端的音乐播放器&#xff0c;支持自定义音源&#xff0c;界面简洁。用户可以通过缓存下载歌曲和歌词。为了使用这些功能&#xff0c;需要先进行音源导入。通过设置中的存储设置&#xff0c;将缓存文件夹移动到download目录下&#xff0c;之后缓存的…

Allegro从.brd文件中导出器件ball map

Step 1&#xff08;可选&#xff09;&#xff1a;设置网络颜色 Step2&#xff1a;File->Export->Symbol Spreadsheet\ Step3&#xff1a;Primary text选择Net Name Step 4&#xff1a;在.brd所有文件夹下生成一个ball map文件&#xff0c;其中网络颜色与Step 1一致。ba…

Docker入门之Windows安装Docker初体验

在之前我们认识了docker的容器&#xff0c;了解了docker的相关概念&#xff1a;镜像&#xff0c;容器&#xff0c;仓库&#xff1a;面试官让你介绍一下docker&#xff0c;别再说不知道了 之后又带大家动手体验了一下docker从零开始玩转 Docker&#xff1a;一站式入门指南&#…

家庭网络常识:猫与路由器

这张图大家应该不陌生——以前家庭网络的连接方式。 图1 家庭网络连接示意图 来说说猫/光猫&#xff1a; 先看看两者的图片。 图2 猫 图3 光猫 这个东西因为英文叫“modem”&#xff0c;类似中文的“猫”&#xff0c;所以简称“猫”。 猫和光猫的区别就是&#xff0c;一…

三种复制只有阅读权限的飞书网络文档的方法

大家都知道&#xff0c;飞书是一款功能强大的在线协作工具&#xff0c;可以帮助团队更高效地协作和沟通。越来越多的资料都在使用飞书文档&#xff0c;在使用飞书的过程中&#xff0c;发现很多文档没有复制权限&#xff0c;如果想要摘抄笔记&#xff0c;只能一个字一个字地敲出…

elasticsearch的倒排索引是什么?

大家好&#xff0c;我是锋哥。今天分享关于【elasticsearch的倒排索引是什么&#xff1f;】面试题。希望对大家有帮助&#xff1b; elasticsearch的倒排索引是什么&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 倒排索引&#xff08;Inverted Index&a…

基于Java Springboot甘肃旅游管理系统

一、作品包含 源码数据库设计文档万字PPT全套环境和工具资源部署教程 二、项目技术 前端技术&#xff1a;Html、Css、Js、Vue、Element-ui 数据库&#xff1a;MySQL 后端技术&#xff1a;Java、Spring Boot、MyBatis 三、运行环境 开发工具&#xff1a;IDEA/eclipse 数据…

uniApp项目运行到鸿蒙手机,应用图标一直是H,应用名一直是HBuilder问题

项目运行到鸿蒙手机&#xff0c;应用图标一直是H,应用名一直是HBuilder问题 应用运行到鸿蒙手机和鸿蒙模拟器&#xff0c;应用图标一直是H,应用名一直是HBuilder&#xff0c;在自动生成的harmony-configs文件夹下也没有配置的文件&#xff0c; 这时候需要你将DevEco Studio 下…