深入理解数据结构:链表

文章目录

    • 🌰导语
    • 🌰链表的定义及基本结构
    • 🌰单链表
      • 🥕单链表特点
    • 🌰双向链表
      • 🥕双链表特点
    • 🌰循环链表
      • 🥕循环链表特点
    • 🌰链表的操作
      • 🍆链表的插入
        • 🫘链头插入
        • 🫘链间插入
      • 🍆链表的删除
        • 🫘链头删除
        • 🫘链间删除
      • 🍆链表的查询
    • 🌰链表的应用场景
    • 🌰链表与数组的比较
        • 🌳存储方式
        • 🌳插入和删除操作
        • 🌳访问效率
        • 🌳空间效率
    • 🌰结语

🌰导语

链表是一种常用的数据结构,通过节点之间的指针连接而成,具有动态性和高效的插入删除操作。本文将深入介绍链表的定义、类型、操作以及应用场景,帮助读者全面理解链表的原理和使用。

🌰链表的定义及基本结构

链表是一种由节点组成的数据结构,每个节点包含存储数据的部分以及指向下一个节点的指针。通过节点之间的指针连接,形成了链表的结构。链表可以分为单链表、双向链表和循环链表等不同类型,它们各自具有特定的特点和应用场景。

  • 数据元素:节点存储的实际数据。数据可以是任意类型,例如整数、字符、字符串、对象等。
  • 指针(或引用):指向下一个节点的指针。它存储了下一个节点在内存中的地址,通过这个指针可以找到链表的下一个节点。

在这里插入图片描述

🌰单链表

单链表是最简单的链表类型,每个节点包含一个数据元素和一个指向下一个节点的指针。单链表的尾节点指针为空,表示链表的结束。单链表的插入、删除操作相对简单高效,但随机访问的效率较低。
结构图:
在这里插入图片描述

🥕单链表特点

  • 非连续存储:单链表中的节点在内存中可以是任意位置,不要求连续存储。每个节点通过指针指向下一个节点,从而将它们连在一起。

  • 动态性:相较于数组,单链表的长度可以动态地增减,不需要预先分配内存空间。这使得单链表在需要频繁插入和删除节点的场景中更加灵活。

  • 搜索效率相对较低:由于单链表的节点只能通过指针一个一个地遍历,因此搜索某个特定的节点需要遍历整个链表,时间复杂度为O(n),其中n是链表的长度。相比之下,数组可以使用索引直接访问元素,搜索效率更高。

  • 内存空间的额外开销:单链表中的每个节点除了存储数据外,还需要存储下一个节点的指针,这导致了额外的内存开销。

  • 插入和删除效率较高:相对于数组,单链表在插入和删除节点时效率较高。插入一个节点只需要改变相邻节点的指针,而删除一个节点只需要改变前一个节点的指针指向下一个节点,不需要移动其他元素。

  • 灵活性:单链表可以方便地进行节点的插入和删除操作,可以根据实际需要进行自由调整。

🌰双向链表

双向链表在单链表的基础上增加了一个指向前一个节点的指针,使得节点既能够往后访问,也能够往前访问。这样的设计使得插入和删除操作更加灵活便捷,但相应地占用了更多的存储空间。
结构图:
在这里插入图片描述

🥕双链表特点

  • 支持双向遍历:相对于单向链表,双向链表支持双向遍历,可以从前往后、从后往前遍历链表。

  • 插入、删除节点效率更高:相较于单向链表,双向链表在插入和删除某个节点时,只需要改变相邻两个节点的指向,效率更高,尤其是在删除链表中某个元素时更加便捷。

  • 需要更多的内存空间:相比单向链表,双向链表需要更多的内存空间来存储额外的指针,增加了额外的空间开销。

  • 内存存储不连续:相对于数组,链表中节点的内存存储位置不连续,需要使用指针进行串联,这可以更加灵活地进行插入、删除和移动节点的操作。

  • 操作复杂度较高:插入、删除、移动等操作,需要修改前后节点的指针信息,操作比较繁琐。

🌰循环链表

循环链表是一种特殊的链表类型,链表的尾节点指针指向头节点,形成一个循环的结构。循环链表可以通过尾节点快速访问链表的头部,常用于某些特定场景,如循环队列的实现。
结构图:
在这里插入图片描述

🥕循环链表特点

  • 首位相连:循环链表的最后一个节点指向链表的第一个节点,使得链表成为一个环形结构。这样,链表的结束节点与开始节点相连,可以实现无限循环,更加灵活和方便。

  • 操作始终成立:由于循环链表始终是一个环形的结构,因此操作(例如插入、删除、查找等)始终处于链表中,这也保证了操作始终能够完成。

  • 遍历循环:与单向链表相比,在遍历时需要注意指针不要陷入死循环。否则会导致遍历永远无法结束。

  • 内存空间的额外开销:与单向链表相比,循环链表需要多一个指向头部节点的指针,增加了额外的空间开销。

  • 插入和删除效率较高:相对于数组,循环链表在插入和删除节点时效率比较高。插入一个节点时只需要修改相邻节点的指针即可,而删除一个节点时只需要改变前一个节点的指针指向下一个节点即可。

🌰链表的操作

链表的常见操作包括插入、删除和查询。链表的插入操作可以在指定位置或者链表尾部插入一个节点,只需要调整相应节点的指针即可。链表的删除操作类似,只需要改变相应节点的指针即可删除指定节点。链表的访问操作需从头节点开始遍历,直到找到目标节点或遍历到链表尾部。同时,需要注意链表操作时需要处理特殊情况,如链表为空或插入删除节点是头节点或尾节点的情况。

🍆链表的插入

🫘链头插入

在这里插入图片描述

代码示例

class Node:def __init__(self, data):self.data = dataself.next = Nonedef insert_at_head(head, value):# 创建新节点new_node = Node(value)# 将新节点的下一个指针指向当前头节点new_node.next = head# 更新头节点为新节点head = new_nodereturn headdef display_linked_list(head):current = headwhile current:print(current.data, "-> ", end="")current = current.nextprint("NULL")# 创建一个空链表
head = None# 在头部插入节点
head = insert_at_head(head, 3)
head = insert_at_head(head, 2)
head = insert_at_head(head, 1)# 打印链表
display_linked_list(head)

打印结果:

1 -> 2 -> 3 -> NULL
🫘链间插入

在这里插入图片描述
程序示例:

class Node:def __init__(self, data):self.data = dataself.next = Nonedef insert_in_between(node, value):if not node:return Nonenew_node = Node(value)new_node.next = node.nextnode.next = new_nodedef display_linked_list(head):current = headwhile current:print(current.data, "-> ", end="")current = current.nextprint("NULL")# 创建一个链表
head = Node(1)
node2 = Node(2)
node3 = Node(3)
node4 = Node(4)head.next = node2
node2.next = node3
node3.next = node4# 在节点2和节点3之间插入节点
insert_in_between(node2, 2.5)# 打印链表
display_linked_list(head)

打印结果:

1 -> 2 -> 2.5 -> 3 -> 4 -> NULL

🍆链表的删除

🫘链头删除

在这里插入图片描述

代码示例:

class Node:def __init__(self, data):self.data = dataself.next = Nonedef delete_at_head(head):if not head:return Nonenew_head = head.nexthead.next = Nonereturn new_headdef display_linked_list(head):current = headwhile current:print(current.data, "-> ", end="")current = current.nextprint("NULL")# 创建一个链表
head = Node(1)
node2 = Node(2)
node3 = Node(3)head.next = node2
node2.next = node3# 删除头节点
head = delete_at_head(head)# 打印链表
display_linked_list(head)

运行结果:

2 -> 3 -> NULL
🫘链间删除

在这里插入图片描述
示例代码:

class Node:def __init__(self, data):self.data = dataself.next = Nonedef delete_in_between(head, value):if not head:return Nonecurrent = headprev = Nonewhile current and current.data != value:prev = currentcurrent = current.nextif not current:return headif not prev:head = head.nextelse:prev.next = current.nextcurrent.next = Nonereturn headdef display_linked_list(head):current = headwhile current:print(current.data, "-> ", end="")current = current.nextprint("NULL")# 创建一个链表
head = Node(1)
node2 = Node(2)
node3 = Node(3)
node4 = Node(4)head.next = node2
node2.next = node3
node3.next = node4# 删除节点3
head = delete_in_between(head, 3)# 打印链表
display_linked_list(head)

运行结果:

1 -> 2 -> 4 -> NULL

🍆链表的查询

代码示例:

class Node:def __init__(self, data):self.data = dataself.next = Nonedef search_node(head, value):current = headwhile current:if current.data == value:return Truecurrent = current.nextreturn False# 创建一个链表
head = Node(1)
node2 = Node(2)
node3 = Node(3)
node4 = Node(4)head.next = node2
node2.next = node3
node3.next = node4# 查询节点3
found = search_node(head, 3)if found:print("节点找到")
else:print("节点未找到")

🌰链表的应用场景

链表在实际应用中有许多重要的应用场景。其中,LRU Cache(最近最少使用缓存)获取和替换缓存中的数据时,可以使用链表实现高效的插入和删除操作。链表反转可以使用链表的插入和删除操作实现,将原链表的节点反向连接,获得反转后的链表。另外,链表排序也是链表的经典应用之一,通过比较和交换链表节点的值,实现链表的排序操作。

🌰链表与数组的比较

链表和数组是两种不同的数据结构,它们在以下方面有所区别:

🌳存储方式

数组是一块连续的内存空间,可以通过下标访问任意位置的元素;而链表是由节点组成,每个节点包含数据和指向下一个节点的指针。

🌳插入和删除操作

在数组中,插入和删除操作可能需要移动其他元素以保持连续性,时间复杂度为 O(n);而链表中插入和删除操作只需要更新节点的指针,时间复杂度通常为 O(1)。

🌳访问效率

在数组中,通过下标可以直接访问元素,时间复杂度为 O(1);而链表需要从头节点开始遍历,平均情况下需要 O(n) 时间。

🌳空间效率

对于相同的元素数量,链表通常需要更多的空间,因为每个节点都需要额外的指针来指向下一个节点;而数组只需要连续的内存空间。

🌰结语

链表作为一种重要的数据结构,在算法和数据结构中扮演着重要的角色。通过深入理解链表的原理和操作,我们可以更好地应用它来解决实际问题。

链表的灵活性使得它在许多领域有着广泛的应用。其中,LRU Cache就是一个很好的例子。LRU Cache是一种常见的缓存策略,它会保留最近最常使用的数据,而淘汰最近最少使用的数据。这可以通过链表来实现。链表的头部表示最近最常使用的数据,而尾部表示最近最少使用的数据。当有新数据加入时,我们可以将其添加到链表的头部。而当缓存满时,我们可以淘汰链表尾部的数据。这就是链表的高效插入和删除操作在实际应用中的体现。

链表的反转也是一个重要的应用场景。通过链表的插入和删除操作,我们可以将原始链表的节点逐个取出,并依次插入到新链表的头部,从而实现链表的反转。这个过程只需要简单地修改指针的指向,时间复杂度为O(n),其中n为链表的长度。链表的反转在实际开发中经常遇到,比如反转字符串、反转链表以及翻转二叉树等。

此外,链表的排序也是另一个常见的应用场景。链表的排序可以采用排序算法中的一些经典方法,比如插入排序、归并排序和快速排序等。其中,归并排序在链表中有着很好的适用性。归并排序是一种分治的排序算法,通过递归地将链表划分为更小的子链表,然后将它们按顺序合并。这样,在每一层递归中,我们可以通过比较两个已排序的子链表的头部节点,选择较小的节点,进行合并。通过这种方式,我们可以实现链表的排序操作。

在选择使用链表还是数组时,我们需要根据实际情况权衡它们之间的优缺点。链表的插入和删除操作相对高效,而数组的随机访问效率较高。因此,在需要频繁插入和删除的场景下,链表是一个更好的选择。而在需要快速根据索引查找元素的场景下,数组更具优势。此外,在空间方面,链表需要额外的指针来连接节点,而数组则需要连续的存储空间。因此,如果内存空间有限,我们可能需要考虑使用链表。

总结起来,链表是一种重要的数据结构,具有许多广泛应用的场景。通过深入理解链表的原理和操作,我们可以更好地应用它来解决实际问题。无论是LRU Cache的实现、链表的反转还是链表的排序,都需要我们熟练掌握链表的特性和操作方法。


🏫博客主页:魔王-T

🏯系列专栏:结构算法

🥝大鹏一日同风起 扶摇直上九万里

❤️感谢大家点赞👍收藏⭐评论✍️


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

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

相关文章

Git 仓库越来越大?不要慌!试试这个简单方法,轻松秒瘦身

开局两张图 瘦身前瘦身后 目录 开局两张图前言下载 BFG克隆代码Git 仓库瘦身清理存储库储存库 GC推送仓库 Git 瘦身验证结语开源项目 前言 在进行项目开发的过程中,代码仓库的体积可能会逐渐增大,特别是在版本控制系统中保留了大量的历史提交记录和不必…

YOLOv5算法进阶改进(4)— 引入解耦合头部 | 助力提高检测准确率

前言:Hello大家好,我是小哥谈。解耦头是目标检测中的一种头部设计,用于从检测网络的特征图中提取目标位置和类别信息。具体来说,解耦头部将目标检测任务分解为两个子任务:分类和回归。分类任务用于预测目标的类别,回归任务用于预测目标的位置。这种设计可以提高目标检测的…

黑马程序员索引学习笔记

文章目录 索引的分类从索引字段特性从物理存储从数据结构组成索引的字段个数 InnoDB主键索的Btree高度为多高呢?explain执行计划最左匹配原则索引失效情况SQL提示覆盖索引、回表查询前缀索引索引设计原则 索引的分类 从索引字段特性 主键索引、唯一索引、常规索引、全文索引…

卷积神经网络(CNN)车牌识别

文章目录 一、前言二、前期工作1. 设置GPU(如果使用的是CPU可以忽略这步)2. 导入数据3. 查看数据3.数据可视化4.标签数字化 二、构建一个tf.data.Dataset1.预处理函数2.加载数据3.配置数据 三、搭建网络模型四、设置动态学习率五、编译六、训练八、保存和…

6.1 Windows驱动开发:内核枚举SSDT表基址

SSDT表(System Service Descriptor Table)是Windows操作系统内核中的关键组成部分,负责存储系统服务调用的相关信息。具体而言,SSDT表包含了系统调用的函数地址以及其他与系统服务相关的信息。每个系统调用对应SSDT表中的一个表项…

PWM(PulseWidthModulation)控制

PWM(Pulse Width Modulation)控制就是对脉冲的宽度进行调制的技术,即通过对一系列脉冲的宽度进行调制,来等效的获得所需要的波形(含形状和幅值);面积等效原理是PWM技术的重要基础理论&#xff1…

软件工程简明教程

软件工程简明教程 何为软件工程? 1968 年 NATO(北大西洋公约组织)提出了软件危机(Software crisis)一词。同年,为了解决软件危机问题,“软件工程”的概念诞生了。一门叫做软件工程的学科也就应…

电源控制系统架构(PCSA)之电源控制框架概览

目录 6 电源控制框架 6.1 电源控制框架概述 6.1.1 电源控制框架低功耗接口 6.1.2 电源控制框架基础设施组件 6 电源控制框架 电源控制框架是标准基础设施组件、接口和相关方法的集合,可用于构建SoC电源管理所需的基础设施。 本章介绍框架的主要组件和低功耗接…

vue3通过v-model实现父子组件通信

单一值传递 父组件 <template><div ><h1>v-model实现父子组件通讯</h1><hr><child1 v-model"num"></child1><!-- 上下两个是等价的 --><child1 :modelValue"num" update:modelValue"handle&quo…

聚类笔记:HDBSCAN

1 算法介绍 DBSCAN/OPTICS层次聚类主要由以下几步组成 空间变换构建最小生成树构建聚类层次结构(聚类树)压缩聚类树提取簇 2 空间变换 用互达距离来表示两个样本点之间的距离 ——>密集区域的样本距离不受影响——>稀疏区域的样本点与其他样本点的距离被放大——>…

速记:一个TL431应用电路

一个TL431应用电路 仿真结果 输出电压为&#xff1a;5V 负载电阻为&#xff1a; R4 50Ω 如果负载R4加重 显然负载加重&#xff0c;输出就达不到5V. 三极管T1 的作用 没有三极管的情况 同样是保持负载 R 50Ω 可见三极管的作用就是用来放大电流

MYSQL基础之【正则表达式,事务处理】

文章目录 前言MySQL 正则表达式MySQL 事务事务控制语句事务处理方法PHP中使用事务实例 后言 前言 hello world欢迎来到前端的新世界 &#x1f61c;当前文章系列专栏&#xff1a;Mysql &#x1f431;‍&#x1f453;博主在前端领域还有很多知识和技术需要掌握&#xff0c;正在不…

文档向量化工具(二):text2vec介绍

目录 前言 text2vec开源项目 核心能力 文本向量表示模型 本地试用 安装依赖 下载模型到本地&#xff08;如果你的网络能直接从huggingface上拉取文件&#xff0c;可跳过&#xff09; ​运行试验代码 前言 在上一篇文章中介绍了&#xff0c;如何从不同格式的文件里提取…

4.整数输入,并输出变量类型【2023.11.26】

1.问题描述 使用 input 函数读入一个整数&#xff0c;并将该整数输出&#xff0c;并在下一行输出变量类型 2.解决思路 使用 type 函数获取变量类型 输入的内容从 input() 函数获取时&#xff0c;它们是字符串类型。但是&#xff0c;我们需要将其转换为整数类型&#xff0c;以…

Unity-类-Vector

Vector矢量 是一个基本的数学概念,它允许你描述方向和大小。在游戏和应用中,矢量通常用于描述一些基本属性,如角色的位置、物体移动的速度或两个物体之间的距离。 矢量算术是计算机编程很多方面(如图形、物理和动画)的基础,深入了解这一主题对于充分发挥 Unity 的功能很有…

【STM32单片机】自动售货机控制系统设计

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用STM32F103C8T6单片机控制器&#xff0c;使用OLED显示模块、矩阵按键模块、LED和蜂鸣器、继电器模块等。 主要功能&#xff1a; 系统运行后&#xff0c;OLED显示系统初始界面&#xff0c;可通过…

JavaEE进阶学习:读取和存储对象

到了学习 JavaEE 这块要有一个思想,实现一个功能的时候,先考虑下有没有实现对应功能的注解. 在 Spring 中想要更简单的存储和读取对象的核心是使用注解&#xff0c;也就是我们接下来要学习 Spring 中的相关注解&#xff0c;来存储和读取 Bean 对象 1.存储 Bean 对象 之前我们…

跟着chatgpt一起学|1.spark入门之MLLib

chatgpt在这一章表现的不好&#xff0c;所以我主要用它来帮我翻译文章提炼信息 1.前言 首先找到spark官网里关于MLLib的链接 spark内一共有2种支持机器学习的包&#xff0c; 一种是spark.ml,基于DataFrame的&#xff0c;也是目前主流的 另一种则是spark.mllib,是基于RDD的…

王者荣耀java版

主要功能 键盘W,A,S,D键&#xff1a;控制玩家上下左右移动。按钮一&#xff1a;控制英雄发射一个矩形攻击红方小兵。按钮二&#xff1a;控制英雄发射魅惑技能&#xff0c;伤害小兵并让小兵停止移动。技能三&#xff1a;攻击多个敌人并让小兵停止移动。普攻&#xff1a;对小兵造…

乘法原理 LeetCode 828. 统计子串中的唯一字符

我们定义了一个函数 countUniqueChars(s) 来统计字符串 s 中的唯一字符&#xff0c;并返回唯一字符的个数。 例如&#xff1a;s "LEETCODE" &#xff0c;则其中 "L", "T","C","O","D" 都是唯一字符&#xff0c;…