深入理解数据结构:链表

文章目录

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

🌰导语

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

🌰链表的定义及基本结构

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

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

在这里插入图片描述

🌰单链表

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

🥕单链表特点

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

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

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

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

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

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

🌰双向链表

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

🥕双链表特点

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

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

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

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

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

🌰循环链表

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

🥕循环链表特点

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

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

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

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

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

🌰链表的操作

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

🍆链表的插入

🫘链头插入

在这里插入图片描述

代码示例

class Node:
    def __init__(self, data):
        self.data = data
        self.next = None
        
def insert_at_head(head, value):
    # 创建新节点
    new_node = Node(value)
    
    # 将新节点的下一个指针指向当前头节点
    new_node.next = head
    
    # 更新头节点为新节点
    head = new_node
    
    return head

def display_linked_list(head):
    current = head
    
    while current:
        print(current.data, "-> ", end="")
        current = current.next
    
    print("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 = data
        self.next = None
        
def insert_in_between(node, value):
    if not node:
        return None
    
    new_node = Node(value)
    new_node.next = node.next
    node.next = new_node
    
def display_linked_list(head):
    current = head
    
    while current:
        print(current.data, "-> ", end="")
        current = current.next
    
    print("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 = data
        self.next = None
        
def delete_at_head(head):
    if not head:
        return None
    
    new_head = head.next
    head.next = None
    
    return new_head

def display_linked_list(head):
    current = head
    
    while current:
        print(current.data, "-> ", end="")
        current = current.next
    
    print("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 = data
        self.next = None

def delete_in_between(head, value):
    if not head:
        return None

    current = head
    prev = None

    while current and current.data != value:
        prev = current
        current = current.next

    if not current:
        return head

    if not prev:
        head = head.next
    else:
        prev.next = current.next

    current.next = None

    return head

def display_linked_list(head):
    current = head

    while current:
        print(current.data, "-> ", end="")
        current = current.next

    print("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 = data
        self.next = None

def search_node(head, value):
    current = head

    while current:
        if current.data == value:
            return True
        current = current.next

    return 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

🏯系列专栏:结构算法

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

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


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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

文件批量改名方法:文件自动批量重命名,提升文件管理效率

在日常工作中随着工作时间的推移,在文件数量日益增长的情况下,会在电脑中积累大量的文件。如果文件名混乱无序,查找和识别重要文件将变得非常困难。这不仅会浪费大量的时间和精力,还可能导致重要文件的丢失或混乱。文件批量改名可…

Java的内部类

文章目录 静态内部类(被static修饰的成员内部类)--可以实例化!获取静态内部类对象(把它当成外部类的成员)静态内部类可以声明普通成员变量和方法,而普通内部类不能声明static成员变量和方法静态内部类跟静态方法一样,只能访问静态的成员变量和方法&#…

【计算机网络笔记】多路访问控制(MAC)协议——随机访问MAC协议

系列文章目录 什么是计算机网络? 什么是网络协议? 计算机网络的结构 数据交换之电路交换 数据交换之报文交换和分组交换 分组交换 vs 电路交换 计算机网络性能(1)——速率、带宽、延迟 计算机网络性能(2)…

[环境配置]vscode免密ssh的设置流程

测试环境: windows 11 ubuntu16.04 vmware 第一步:生成密钥 cmd打开输入:ssh-keygen -t rsa 一路回车后可以在C:\Users\用户名\.ssh路径看到id_rsa.pub,我们打开这个文件,用记事本打开即可,然后复制里…

代码随想录算法训练营第四十六天【动态规划part08】 | 139.单词拆分、背包总结

139.单词拆分 题目链接: 力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台 求解思路: 单词是物品,字符串s是背包,单词能否组成字符串s,就是问物品能不能把背包装满。 动规五部曲 确定dp数…

VCenter6.7 Web访问提示503 Service Unavailable

PS:本文分享VMware Vcenter在web登录的时候报错:503 Service Unavailable,对于6.7.x版本比较适用,其他版本需自行测试。 简单来讲就是需要重启一下vsphre-client服务,如重启该服务仍无法解决,可以尝试重启一…

从0开始学习JavaScript--JavaScript函数返回值

在JavaScript中,函数是一种强大的工具,不仅能够执行一系列操作,还可以返回值。理解函数返回值的概念对于编写清晰、灵活的代码至关重要。本文将深入探讨JavaScript函数返回值的各种方面,包括基本返回值、多返回值、异步函数的返回…

MySQL MHA高可用架构搭建

快捷查看指令 ctrlf 进行搜索会直接定位到需要的知识点和命令讲解(如有不正确的地方欢迎各位小伙伴在评论区提意见,博主会及时修改) MySQL MHA高可用架构搭建 MHA(Master HA)是一款开源的 MySQL 的高可用程序&#xf…

电磁场信息论及先进MIMO (黄大年茶思屋座谈) 笔记

天线阵的负载动态调控,动态阻抗匹配网络,实时跟着扫描角度的变化而变化,可能突破Hannan极限。 新的天线构架: 周期 —》非周期 每个单元不一样 动态可调,可重构 每个天线多端口或多模式 多层天线 非周期结构天线的增…

#define例题

我们已经学了#define的所有知识,让我们来看这道题,可不要又陷入陷阱 题目要求: #define N 4 #define Y(n) ((N2)*n) int main() {int z 2 * (N Y(5 1));printf("z%d\n", z);return 0; } 求这个z的值是多少? 我们直接…

YOLOv8 训练自己的分割数据集

之前写过一篇 使用YOLOv8训练自己的【目标检测】数据集-【收集数据集】-【标注数据集】-【划分数据集】-【配置训练环境】-【训练模型】-【评估模型】-【导出模型】,里面带大家整个流程走过一遍了, 这篇文章我们来介绍如何使用 YOLOv8 训练分割数据集&a…

旋转框检测项目相关python库知识总结(mmrotate、ppyolo_r、yolov5_obb)

旋转框常用于检测带有角度信息的矩形框,即矩形框的宽和高不再与图像坐标轴平行。相较于水平矩形框,旋转矩形框一般包括更少的背景信息。旋转框检测常用于遥感等场景中,本博文简单的介绍了可应用于旋转框数据训练的开源库,数据结构…

佳易王各行业收银管理系统软件,企业ERP管理软件,企业或个体定制开发软件以及软件教程资源下载总目录,持续更新,可关注收藏查阅

系统简介 1、佳易王软件功能实用、操作简单、软件绿色免安装,解压即可使用,软件已经内置数据库,不需再安装其他数据库文件。 2、佳易王软件,已经形成系列,上百款管理系统软件涵盖多个行业。 3、已为多个企业个体定制…

【c++】——类和对象(下) ——内存管理

作者:chlorine 专栏:c专栏 目录 💻 C/C内存分布 💻C语言中动态内存管理方式:malloc/calloc/realloc/free ​编辑 💻C内存管理方式 👉new/delete操作内置类型 👉new和delete操作自定义类型 &#x1f…

makefile 学习(5)完整的makefile模板

参考自: (1)深度学习部署笔记(二): g, makefile语法,makefile自己的CUDA编程模板(2)https://zhuanlan.zhihu.com/p/396448133(3) 一个挺好的工程模板,(https://github.com/shouxieai/cpp-proj-template) 1. c 编译流…

Linux加强篇004-Vim编辑器与Shell命令脚本

目录 前言 1. Vim文本编辑器 1.1 编写简单文档 1.2 配置主机名称 1.3 配置网卡信息 1.4 配置软件仓库 2. 编写Shell脚本 2.1 编写简单的脚本 2.2 接收用户的参数 2.3 判断用户的参数 3. 流程控制语句 3.1 if条件测试语句 3.2 for条件循环语句 3.3 while条件循环语…

深入浅出 Linux 中的 ARM IOMMU SMMU II

SMMU 驱动中的系统 I/O 设备探测 要使系统 I/O 设备的 DMA 内存访问能通过 IOMMU,需要将系统 I/O 设备和 IOMMU 设备绑定起来,也就是执行 SMMU 驱动中的系统 I/O 设备探测。总线发现系统 I/O 设备并和对应的驱动程序绑定,与 IOMMU 设备驱动程…

死锁是什么?死锁是如何产生的?如何破除死锁?

1. 死锁是什么 多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。 2. 死锁的三种典型情况 一个线程, 一把锁, 是不可重入锁, 该线程针对这个锁连续加锁两次, 就会出现死锁. 两个线程…

Java多态:多态多态,多么变态

👑专栏内容:Java⛪个人主页:子夜的星的主页💕座右铭:前路未远,步履不停 目录 一、重写1、重写的规则2、重写与重载的区别 二、多态1、多态的概念2、多态的实现3、向上转移和向下转型Ⅰ、向上转型Ⅱ、向下转…

蓝桥杯官网算法赛(蓝桥小课堂)

问题描述 蓝桥小课堂开课啦! 海伦公式(Herons formula),也称为海伦-秦九韶公式,是用于计算三角形面积的一种公式,它可以通过三条边的长度来确定三角形的面积,而无需知道三角形的高度。 海伦公…