四、链表————相关概念详解

news2024/12/28 5:15:50

链表

  • 前言
  • 一、链表是什么?
  • 二、链表的类型
    • 2.1 单向链表
    • 2.2 环形链表
    • 2.3 双向链表
  • 三、链表中常用操作 (以单向列表为例)
    • 3.1 初始化链表
    • 3.2 判断链表是否为空
    • 3.3 获取链表长度
    • 3.4 插入节点
      • 3.4.1 链表头部添加节点
      • 3.4.2 链表尾部添加节点
      • 3.4.3 指定位置添加节点
    • 3.5 删除节点
    • 3.6 查找节点是否存在
    • 3.7 遍历整个链表
  • 四、数组跟链表的区别
  • 总结


前言

  • 在上一章我们学习了数组相关知识,我们知道数组在内存中是连续存在的,而当数组非常大时,内存可能无法提供如此大的连续空间。此时链表的灵活性优势就体现出来了。

一、链表是什么?

  • 链表是一种线性的数据结构,其每个节点都都分为数值域与地址域,数值域中存储的是节点的数据,地址域中存储的是下一个 节点的地址,这样就可以把各个节点链接起来。
  • 链表的设计如下图所示:
    在这里插入图片描述
  • 观察上图我们发现,链表由节点对象组成的,每个对象有两个属性:一个是数值域,一个是地址域。
    • 链表的首个节点称为头节点,最后一个节点叫做尾节点
    • 尾节点指向空,在Python中被记作None

代码演示 创建节点类 :

class SingleNode(object):
    """
    创建节点类
    """
    def __init__(self, val = None):
        """
        节点的 初始化方法  
        :param item: 接受这个节点 数值域的数据
        """
        self.val = val  
        self.next = None  # 将 节点的地址域先定义为None

二、链表的类型

2.1 单向链表

  • 即前面介绍的普通链表。单向链表的节点包含值和指向下一节点的引用两项数据。我们将首个节点称为头节点,将最后一个节点称为尾节点,尾节点指向空 None 。
  • 在这里插入图片描述

2.2 环形链表

  • 如果我们令单向链表的尾节点指向头节点(首尾相接),则得到一个环形链表。在环形链表中,任意节点都可以视作头节点。
  • 在这里插入图片描述

2.3 双向链表

  • 与单向链表相比,双向链表记录了两个方向的引用。双向链表的节点定义同时包含指向后继节点(下一个节点)和前驱节点(上一个节点)的引用(指针)。相较于单向链表,双向链表更具灵活性,可以朝两个方向遍历链表,但相应地也需要占用更多的内存空间。
  • 在这里插入图片描述

三、链表中常用操作 (以单向列表为例)

3.1 初始化链表

  • 首先是要初始化链表属性,使用虚拟头节点,头节点指向链表的第一个节点

代码如下(示例):

class SingleLinkedList(object):
    # 初始化属性.
    def __init__(self, node = None):
        self.head = node   # head: 代表头结点

3.2 判断链表是否为空

代码如下(示例):

def is_empty(self):
        # 思路1: 判断头结点(self.head) 是否为 None, 是: 空, 否: 不为空.
        # if self.head == None:
        #     return True
        # else:
        #     return False

        # 思路2: 上述代码的, 三元写法.
        return True if self.head == None else False

        # 思路3: 最终版, ==的结果本身就是: True 或者 False, 直接返回.
        # return self.head == None

3.3 获取链表长度

代码如下(示例):

class SingleLinkedList(object):
    def length(self):
    	 # 1. 定义变量 cur, 记录当前节点, 从头结点开始.
        cur = self.head  # current: 当前
        # 2. 定义count变量, 计数.
        count = 0
        # 3. 判断当前节点是否为None, 如果不是, 就循环.
        while cur is not None:
            # 4. 每次循环, 计数器都+1, 然后获取下个节点.
            count += 1
            cur = cur.next  # 获取下个节点.
        # 5. 走到这里, 循环结束, 即: 链表长度统计完成, 返回结果即可.
        return count

3.4 插入节点

在链表节点 n0 和 n1 之间插入新节点N:

在这里插入图片描述

3.4.1 链表头部添加节点

代码如下(示例):

class SingleLinkedList(object):
	# 定义  函数 head_add 用来在链表头部添加节点
	def head_add(self, val):
		# 1. 把要添加的元素封装成 新节点.
        new_node = SingleNode(val)
        # 2. 用 新节点的地址域 指向 头结点的地址.
        new_node.next = self.head
        # 3. 设置 新节点为 新的头结点即可.
        self.head = new_node

3.4.2 链表尾部添加节点

代码如下(示例):

class SingleLinkedList(object):
	# 定义  函数 tail_add 用来在链表尾部添加节点
	def tail_add(self, val):
		# 1. 把要添加的元素封装成 新节点.
        new_node = SingleNode(val)
		# 2. 如果链表为空,则新节点直接充当头结点(使用上边我们定义的获取长度的函数)
		if self.length() == 0:
			self.head = new_node
		else:
			# 3 走这里, 链表不为空, 获取链表的最后一个节点即可
			# 使用 cur 来充当指针
			cur = self.head
			# 4. 设置循环 从头边遍历到尾部
			while cur is not None:
				cur = cur.next
			# 5. 走到这里 , cur.next = None, 也就是最后一个节点
			cur.next = new_node 

3.4.3 指定位置添加节点

代码如下(示例):

class SingleLinkedList(object):
	# 定义  函数 head_add 用来在链表尾部添加节点
	# pop 表示索引  val 表示要插入的元素值
	def insert(self, pop, val):
		# 1. 把要添加的元素封装成 新节点.
		new_node = SingleNode(val)
		# 2. 如果 插入的位置是否 小于等于 链表长度, 如果小于链表长度的话,
		# 那就说明是空链表,此时应该插入到最前面,也就是上边我们的往头部插入元素。
		if pop <= 0:
			self.head_add(val)
		# 3. 如果 插入的位置 大于或者等于 链表长度 , 那就往尾部插入元素
		elif pop >= self.length():
			self.tail_add(val)		
		else:
			# 4. 如果是中间插入, 就走如下的逻辑.
            # 5. 把要插入的元素封装成: 新节点.
            new_node = SingleNode(val)
            # 6. 定义变量cur, 表示: 插入位置前的那个节点.
            cur = self.head
            # 7. 定义变量count, 初值为0, 表示插入位置前的哪个"索引"
            count = 0
            # 8. 只要 count < pos - 1 就一直循环, 并逐个获取下个节点.
            while count < pos - 1:  # 因为我们获取的地址域(即: 下个节点的地址), 只要找到前前对象, 它的地址域, 就是前对象.
                                    # 比如说: 要第2个节点, 只要找到第1个节点即可, 它的地址域(next)就是: 第2个节点.
                cur = cur.next
                count += 1          # 计数器+1
            # 9. 循环结束后, cur就是要插入位置前的 那个节点. 把它(cur)的地址域赋值 给 新节点的地址域.
            new_node.next = cur.next
            # 10. 把新节点的 地址 赋值给 cur节点的 地址域.
            cur.next = new_node

3.5 删除节点

删除链表中的节点的示意图如下:

在这里插入图片描述

代码如下(示例):

class SingleLinkedList(object):
	def remove(self, val):
        # 1. 定义变量cur, 代表: 当前节点, 即: 要删除的节点.
        cur = self.head
        # 2. 定义变量pre(previous), 代表: 当前节点的前一个节点.
        pre = None
        # 3. 遍历链表, 获取到每个节点.
        while cur is not None:
            # 4. 判断当前节点的 数值域 是否和要被删除的内容一致.
            if cur.val== val:
                # 5. 如果一致, 判断当前节点是否是头结点, 是, 就直接指向它的地址域(第2个节点)即可.
                if cur == self.head:
                    self.head = cur.next        # 如果要删头结点, 直接让 head指向 第2个节点即可.
                else:
                    # 6. 如果要删除的节点不是头结点,
                    pre.next = cur.next
                # 核心细节: 删除完毕后, 记得: break, 结束删除操作.
                break
            else:
                # 7. 如果不一致, 当前就是(前1个节点了), 然后当前节点为: 它的下个节点.
                pre = cur       # 当前节点: 就是下次判断的 前个节点
                cur = cur.next  # 当前节点: 变更为它的下个节点.
  • 但是删除完成之后, 虽然 N 仍然指向 n1,但是遍历链表已经找不到该节点,因此我们认为这个节点已经被删除了(也可以多加一步,让 N.next = None 虽然会让 N 不再指向 n1 ,但实际上没什么用)

3.6 查找节点是否存在

代码如下(示例):

class SingleLinkedList(object):
	def search(self, val):
        # 1. 定义变量cur, 表示当前节点, 默认从: 头结点开始.
        cur = self.head
        # 2. 遍历链表, 获取到每个节点.
        while cur is not None:
            # 3. 判断当前节点的数值域 是否和 要查找的值一致, 如果一致, 就返回True
            if cur.val== val:
                return True
            # 4. 如果没找到, 当前节点就变更为: 它的下个节点
            cur = cur.next

        # 5. 走到这里, 循环结束, 表示没有找到. 返回False即可.
        return False

3.7 遍历整个链表

代码如下(示例):

class SingleLinkedList(object):
	def travel(self):
        # 1. 获取头结点, 充当: 当前节点.
        cur = self.head
        # 2. 只要当前节点不为空, 就一直遍历.
        while cur is not None:
            # 3. 先打印当前节点的 数值域, 然后获取下个节点.
            print(cur.val)
            cur = cur.next

四、数组跟链表的区别

  • 由于它们采用两种相反的存储策略,因此各种性质和操作效率也呈现对立的特点。
/数组链表
存储方式连续内存空间分散内存空间
容量扩展长度不可变可灵活扩展
内存效率元素占用内存少、但可能浪费空间元素占用内存多
访问元素 O ( 1 ) O(1) O(1) O ( n ) O(n) O(n)
添加元素 O ( n ) O(n) O(n) O ( 1 ) O(1) O(1)
删除元素 O ( n ) O(n) O(n) O ( 1 ) O(1) O(1)

总结

  • 以上就是链表的概念跟基本操作。

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

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

相关文章

大数据处理技术:分布式文件系统HDFS

目录 1 实验名称&#xff1a; 2 实验目的 3 实验内容 4 实验原理 5 实验过程或源代码 5.1 HDFS的基本操作 5.2 HDFS-JAVA接口之读取文件 5.3 HDFS-JAVA接口之上传文件 5.4 HDFS-JAVA接口之删除文件 6 实验结果 6.1 HDFS的基本操作 6.2 HDFS-JAVA接口之读取文件 6.…

精通推荐算法30:行为序列建模之SIM— 基于检索建模长周期行为序列

1 行为序列建模总体架构 2 SIM提出的背景 MIMN第一次真正实现了长周期行为序列的建模&#xff0c;并取得了非常不错的业务效果。但受困于离线建模&#xff0c;它没办法实现用户行为序列和候选物品的交叉。同时采用一个固定大小的记忆网络来压缩超长序列&#xff0c;存在网络容…

redis-shake v4全量增量同步redis数据

1 概述 RedisShake是一个用于处理和迁移 Redis 数据的工具&#xff0c;github地址是https://github.com/tair-opensource/RedisShake。它提供以下特性&#xff1a; 1&#xff09;Redis 兼容性&#xff1a; RedisShake 兼容从 2.8 到 7.2 的 Redis 版本&#xff0c;并支持各种部…

Spring Event 业务解耦神器(泛型喔!)

一.前言 又与我一直负责Cocos Creator的开发,我发现在TS领域,是可以自定义事件(有兴趣的大宝可以坐飞机直达:[CocosCreator]自定义事件(订阅/发布)管理器),这样做有什么好处呢?回答:解耦! 于是乎,我就觉得前端能干的事,后端也一样能干!当然,如果后端是TS或JS写的,比如nodeJS,…

Cyber Weekly #24

赛博新闻 1、OpenAI发布最强模型o1 本周四&#xff08;9月12日&#xff09;&#xff0c;OpenAI宣布推出OpenAIo1系列模型&#xff0c;标志着AI推理能力的新高度。o1系列包括性能强大的o1以及经济高效的o1-mini&#xff0c;适用于不同复杂度的推理任务。新模型在科学、编码、数…

自动排课管理系统(源代码+论文+开题报告)

一、题目摘要 题目简要说明&#xff1a; 选排课系统功能的设计上&#xff0c;选排课系统可以分为登录、排课和选课3个子系统。登录子系统区分排课者(也即系统的管理者)、教师和学生这三者的不同身份&#xff0c;给出不同的权限&#xff0c;在页面中根据身份判断其相应具有的功…

Java 入门指南:JVM(Java虚拟机)——类的生命周期与加载过程

文章目录 类的生命周期类加载过程1&#xff09;载入&#xff08;Loading&#xff09;2&#xff09;验证&#xff08;Verification&#xff09;文件格式验证符号引用验证 3&#xff09;准备&#xff08;Preparation&#xff09;4&#xff09;解析&#xff08;Resolution&#xf…

FreeRTOS—任务通知

一&#xff0c;概念介绍 队列、信号量、事件组等IPC技术都需要创建一个中间对象进程之间通过这些中间对象进行通讯或同步。创建对象就需要分配内存&#xff0c;占用一定内存。 二&#xff0c;任务通知的特点&#xff1a; 一个任务或ISR向另外一个指定的任务发送通知&#xff0c…

2024年最新版Vue3学习笔记

本篇文章是记录来自尚硅谷禹神2023年课程的学习笔记&#xff0c;不得不说禹神讲的是真的超级棒&#xff01; 文章目录 创建Vue3工程main.ts文件解析初始化项目写一个简单的效果 Vue3核心语法setup函数setup和选项式的区别setup语法糖指定组件名称 响应式数据ref函数定义基本类…

C#学习系列之Gmap地图界面上的实时绘制问题

C#学习系列之Gmap地图界面上的实时绘制问题 前言总结 前言 在地图控件上增加绘制不规则图形&#xff0c;在之前的经验来看&#xff0c; System.InvalidOperationException:“无法使用 DependencyObject&#xff0c;它属于其父 Freezable 之外的其他线程。” 其实就是ui线程中…

9.15javaweb项目总结

1.贴吧界面算是完成了基本的 能通过url打开多个贴吧信息的界面了&#xff0c;界面水平不是很高&#xff0c;界面还有待提升&#xff0c;然后该界面的功能点还差点有点远&#xff0c;完成度不是很高。 2.解决了关注的功能问题 要考虑的地方有点多&#xff0c;最简单的就是点击…

SpringSecurity 5

springSecurity是spring的一个顶级项目 也是一个安全框架&#xff0c;可以在spring框架中直接引用。 springSecurity基于RBAC用来处理登录功能和各种权限校验。 〇、配置和运行springSecurity 导入security启动器和web启动器&#xff0c;写一个springBoot的启动类&#xff0c;可…

VMamba: Visual State Space Model 论文总结

题目&#xff1a;VMamba: Visual State Space Model&#xff08;视觉状态空间模型&#xff09; 论文&#xff1a;[2401.10166] VMamba: Visual State Space Model (arxiv.org) 源码&#xff1a;https://arxiv.org/pdf/2401.10166 (github.com) 目录 一、摘要 二、引言 三、方…

URP 线性空间 ui资源制作规范

前言&#xff1a; 关于颜色空间的介绍&#xff0c;可参阅 unity 文档 Color space URP实现了基于物理的渲染&#xff0c;为了保证光照计算的准确&#xff0c;需要使用线性空间&#xff1b; 使用线性空间会带来一个问题&#xff0c;ui资源在unity中进行透明度混合时&#xff…

嵌入式常用轻量级校验算法

在嵌入式中涉及通信基本都需要用到校验算法&#xff0c;如&#xff1a;UART常用的奇偶校验、CAN通信常用的CRC校验等等。下面我们将介绍几种常用的校验算法&#xff1a; 一、校验和 校验和是最基本&#xff0c;也是嵌入式软件工程师最常用的一种校验算法&#xff0c;其实现方法…

jdk知识

jdk,jre,jvm jdk>jre>jvm jdk的bin目录下有编译工具&#xff0c;平时写完java文件用jdk编译&#xff1b; jre的lib文件夹里面是java的jar包(.class文件)&#xff0c;用来给jvm运行编译后的.class文件。 jvm&#xff1a;运行.class文件&#xff0c;.class文件读入到虚拟机…

JVM 垃圾回收机制和GC案例分析

1. 引言 Java 虚拟机&#xff08;JVM&#xff09;的垃圾回收&#xff08;Garbage Collection, GC&#xff09;机制&#xff0c;是自动内存管理的重要组成部分。它通过回收不再使用的对象&#xff0c;避免手动释放内存的麻烦。然而&#xff0c;随着系统复杂性的增加&#xff0c…

C/C++——野指针处理

在C++中,“野指针”(dangling pointer)指的是指向已释放或无效内存的指针。使用野指针可能导致程序崩溃或产生未定义行为。避免野指针的关键在于确保指针始终指向有效内存。下面是一些避免野指针的方法和最佳实践: 1、释放内存后置空指针 当释放掉分配的动态内存后,将指…

文本到3D生成

文本到3D生成是一种通过文本描述直接创建三维数字模型的技术。这种技术能够将语言描述转换成可视化的三维模型&#xff0c;使得内容创作者和设计师可以直接从概念阶段跳转到三维可视化&#xff0c;大大加快创作流程并提供更直观的设计和修改过程。 该技术的核心应用之一是基于…

【乐吾乐大屏可视化组态编辑器】使用手册

1 总览 开始设计&#xff1a;大屏可视化设计器 - 乐吾乐Le5le 1.1 画布 画布即绘画区域&#xff0c;将图形拖拽到画布进行编辑&#xff0c;绘制大屏。 1.2 菜单栏 顶部菜单导航&#xff0c;一级菜单可设置Logo、公司名称、文件编辑、常用编辑、查看、帮助&#xff0c;设置大…