01.Linked-List-Basic

1. 链表简介

1.1 链表定义

链表(Linked List):一种线性表数据结构。它使用一组任意的存储单元(可以是连续的,也可以是不连续的),来存储一组具有相同类型的数据。

简单来说,「链表」 是实现线性表链式存储结构的基础。

以单链表为例,链表的存储方式如下图所示。

如上图所示,链表通过将一组任意的存储单元串联在一起。其中,每个数据元素占用若干存储单元的组合称为一个「链节点」。为了将所有的节点串起来,每个链节点不仅要存放一个数据元素的值,还要存放一个指出这个数据元素在逻辑关系上的直接后继元素所在链节点的地址,该地址被称为「后继指针 next」。

在链表中,数据元素之间的逻辑关系是通过指针来间接反映的。逻辑上相邻的数据元素在物理地址上可能相邻,可也能不相邻。其在物理地址上的表现是随机的。

我们先来简单介绍一下链表结构的优缺点:

  • 优点:存储空间不必事先分配,在需要存储空间的时候可以临时申请,不会造成空间的浪费;一些操作的时间效率远比数组高(插入、移动、删除元素等)。

  • 缺点:不仅数据元素本身的数据信息要占用存储空间,指针也需要占用存储空间,链表结构比数组结构的空间开销大。

接下来先来介绍一下除了单链表之外,链表的其他几种类型。

1.2 双向链表

双向链表(Doubly Linked List):链表的一种,也叫做双链表。它的每个链节点中有两个指针,分别指向直接后继和直接前驱。

从双链表的任意一个节点开始,都可以很方便的访问它的前驱节点和后继节点。

1.3 循环链表

循环链表(Circular linked list):链表的一种。它的最后一个链节点指向头节点,形成一个环。

从循环链表的任何一个节点出发都能找到任何其他节点。

接下来我们以单链表为例,介绍一下链表的基本操作。

2. 链表的基本操作

数据结构的操作一般涉及到增、删、改、查 4 种情况,链表的操作也基本上是这 4 种情况。我们一起来看一下链表的基本操作。

2.1 链表的结构定义

链表是由链节点通过 next 链接而构成的,所以先来定义一个简单的链节点类,即 ListNode 类。ListNode 类使用成员变量 val 表示数据元素的值,使用指针变量 next 表示后继指针。

然后再定义链表类,即 LinkedList 类。ListkedList 类中只有一个链节点变量 head 用来表示链表的头节点。

我们在创建空链表时,只需要把相应的链表头节点变量设置为空链接即可。在 Python 里可以将其设置为 None,其他语言也有类似的惯用值,比如 NULLnil0 等。

「链节点以及链表的结构定义」 代码如下:

# 链节点类
class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

# 链表类
class LinkedList:
    def __init__(self):
        self.head = None

2.2 建立一个线性链表

建立一个线性链表的过程是:根据线性表的数据元素动态生成链节点,并依次将其连接到链表中。其做法如下:

  1. 从所给线性表的第 1 个数据元素开始依次获取表中的数据元素。
  2. 每获取一个数据元素,就为该数据元素生成一个新节点,将新节点插入到链表的尾部。
  3. 插入完毕之后返回第 1 个链节点的地址。

建立一个线性链表的时间复杂度为 O ( n ) O(n) O(n) n n n 为线性表长度。

「建立一个线性链表」 的代码如下:

# 根据 data 初始化一个新链表
def create(self, data):
    self.head = ListNode(0)
    cur = self.head
    for i in range(len(data)):
        node = ListNode(data[i])
        cur.next = node
        cur = cur.next

2.3 求线性链表的长度

线性链表的长度被定义为链表中包含的链节点的个数。求线性链表的长度操作只需要使用一个可以顺着链表指针移动的指针变量 cur 和一个计数器 count。具体做法如下:

  1. 让指针变量 cur 指向链表的第 1 个链节点。
  2. 然后顺着链节点的 next 指针遍历链表,指针变量 cur 每指向一个链节点,计数器就做一次计数。
  3. cur 指向为空时结束遍历,此时计数器的数值就是链表的长度,将其返回即可。

求线性链表的长度操作的问题规模是链表的链节点数 n n n,基本操作是 cur 指针的移动,操作的次数为 n n n,因此算法的时间复杂度为 O ( n ) O(n) O(n)

「求线性链表长度」 的代码如下:

# 获取链表长度
def length(self):
    count = 0
    cur = self.head
    while cur:
        count += 1
        cur = cur.next 
    return count

2.4 查找元素

在链表中查找值为 val 的位置:链表不能像数组那样进行随机访问,只能从头节点 head 开始,沿着链表一个一个节点逐一进行查找。如果查找成功,返回被查找节点的地址。否则返回 None

查找元素操作的问题规模是链表的长度 n n n,而基本操作是指针 cur 的移动操作,所以查找元素算法的时间复杂度为 O ( n ) O(n) O(n)

「在链表中查找元素」 的代码如下:

# 查找元素
def find(self, val):
    cur = self.head
    while cur:
        if val == cur.val:
            return cur
        cur = cur.next

    return None

2.5 插入元素

链表中插入元素操作分为三种:

  • 链表头部插入元素:在链表第 1 个链节点之前插入值为 val 的链节点。
  • 链表尾部插入元素:在链表最后 1 个链节点之后插入值为 val 的链节点。
  • 链表中间插入元素:在链表第 i 个链节点之前插入值为 val 的链节点。

接下来我们分别讲解一下。

2.5.1 链表头部插入元素

算法实现的步骤为:

  1. 先创建一个值为 val 的链节点 node
  2. 然后将 nodenext 指针指向链表的头节点 head
  3. 再将链表的头节点 head 指向 node

因为在链表头部插入链节点与链表的长度无关,所以该算法的时间复杂度为 O ( 1 ) O(1) O(1)

「在链表头部插入值为 val 元素」 的代码如下:

# 头部插入元素
def insertFront(self, val):
    node = ListNode(val)
    node.next = self.head
    self.head = node
2.5.2 尾部插入元素

算法实现的步骤为:

  1. 先创建一个值为 val 的链节点 node
  2. 使用指针 cur 指向链表的头节点 head
  3. 通过链节点的 next 指针移动 cur 指针,从而遍历链表,直到 cur.next == None
  4. cur.next 指向将新的链节点 node

因为将 cur 从链表头部移动到尾部的操作次数是 n n n 次,所以该算法的时间复杂度是 O ( n ) O(n) O(n)

「在链表尾部插入值为 val 的元素」 的代码如下:

# 尾部插入元素
def insertRear(self, val):
    node = ListNode(val)
    cur = self.head
    while cur.next:
        cur = cur.next
    cur.next = node
2.5.3 中间插入元素

算法的实现步骤如下:

  1. 使用指针变量 cur 和一个计数器 count。令 cur 指向链表的头节点,count 初始值赋值为 0
  2. 沿着链节点的 next 指针遍历链表,指针变量 cur 每指向一个链节点,计数器就做一次计数。
  3. count == index - 1 时,说明遍历到了第 index - 1 个链节点,此时停止遍历。
  4. 创建一个值为 val 的链节点 node
  5. node.next 指向 cur.next
  6. 然后令 cur.next 指向 node

因为将 cur 从链表头部移动到第 i 个链节点之前的操作平均时间复杂度是 O ( n ) O(n) O(n),所以该算法的时间复杂度是 O ( n ) O(n) O(n)

「在链表第 i 个链节点之前插入值为 val 的元素」 的代码如下:

# 中间插入元素
def insertInside(self, index, val):
    count = 0
    cur = self.head
    while cur and count < index - 1:
        count += 1
        cur = cur.next
        
    if not cur:
        return 'Error'
    
    node = ListNode(val)
    node.next = cur.next
    cur.next = node

2.6 改变元素

将链表中第 i 个元素值改为 val:首先要先遍历到第 i 个链节点,然后直接更改第 i 个链节点的元素值。具体做法如下:

  1. 使用指针变量 cur 和一个计数器 count。令 cur 指向链表的头节点,count 初始值赋值为 0
  2. 沿着链节点的 next 指针遍历链表,指针变量 cur 每指向一个链节点,计数器就做一次计数。
  3. count == index 时,说明遍历到了第 index 个链节点,此时停止遍历。
  4. 直接更改 cur 的值 val

因为将 cur 从链表头部移动到第 i 个链节点的操作平均时间复杂度是 O ( n ) O(n) O(n),所以该算法的时间复杂度是 O ( n ) O(n) O(n)

「将链表中第 i 个元素值改为 val 的代码如下:

# 改变元素
def change(self, index, val):
    count = 0
    cur = self.head
    while cur and count < index:
        count += 1
        cur = cur.next
        
    if not cur:
        return 'Error'
    
    cur.val = val

2.7 删除元素

链表的删除元素操作同样分为三种情况:

  • 链表头部删除元素:删除链表的第 1 个链节点。
  • 链表尾部删除元素:删除链表末尾最后 1 个链节点。
  • 链表中间删除元素:删除链表第 i 个链节点。

接下来我们分别讲解一下。

2.7.1 链表头部删除元素

链表头部删除元素的方法很简单,具体步骤如下:

  • 直接将 self.head 沿着 next 指针向右移动一步即可。

因为只涉及到 1 步移动操作,所以此算法的时间复杂度为 O ( 1 ) O(1) O(1)

「链表头部删除元素」 的代码如下所示:

# 链表头部删除元素
def removeFront(self):
    if self.head:
        self.head = self.head.next
2.7.2 链表尾部删除元素

链表尾部删除元素的方法也比较简单,具体步骤如下:

  • 先使用指针变量 cur 沿着 next 指针移动到倒数第 2 个链节点。
  • 然后将此节点的 next 指针指向 None 即可。

因为移动到链表尾部的操作次数为 n n n 次,所以该算法的时间复杂度为 O ( n ) O(n) O(n)

「链表尾部删除元素」 的代码如下所示:

# 链表尾部删除元素
def removeRear(self):
    if not self.head.next:
        return 'Error'

    cur = self.head
    while cur.next.next:
        cur = cur.next
    cur.next = None
2.7.3 链表中间删除元素

删除链表中第 i 个元素的算法具体步骤如下:

  1. 先使用指针变量 cur 移动到第 i - 1 个位置的链节点。
  2. 然后将 curnext 指针,指向要第 i 个元素的下一个节点即可。

「删除链表中第 i 个元素」 的代码如下所示:

# 链表中间删除元素
def removeInside(self, index):
    count = 0
    cur = self.head
    
    while cur.next and count < index - 1:
        count += 1
        cur = cur.next
        
    if not cur:
        return 'Error'
        
    del_node = cur.next
    cur.next = del_node.next

到这里,有关链表的基础知识就介绍完了。下面进行一下总结。

3. 链表总结

链表是最基础、最简单的数据结构。「链表」 是实现线性表的链式存储结构的基础。它使用一组任意的存储单元(可以是连续的,也可以是不连续的),来存储一组具有相同类型的数据。

链表最大的优点在于可以灵活的添加和删除元素。链表进行访问元素、改变元素操作的时间复杂度为 O ( n ) O(n) O(n),进行头部插入、头部删除元素操作的时间复杂度是 O ( 1 ) O(1) O(1),进行尾部插入、尾部删除操作的时间复杂度是 O ( n ) O(n) O(n)。普通情况下进行插入、删除元素操作的时间复杂度为 O ( n ) O(n) O(n)

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

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

相关文章

2.1(TCP)

TCP—传输控制协议 是一种面向连接的可靠传输协议。可靠、有序、无丢弃和不重复。 特点&#xff1a; TCP是面向连接&#xff08;虚连接&#xff09;的传输层协议每一条TCP连接有且只能有两个端点。可靠、有序、无丢弃和不重复。TCP协议提供全双工通讯。 发送缓存 存放发送方…

phpStudy安装thinkCMF8时,如何解决服务器rewrite和APIrewrite不支持的问题

解决步骤&#xff1a; 一&#xff1a;服务器rewrite 点击后面的问号跳转到官方文档链接&#xff1a; 复制红框内的代码 打开phpstudy&#xff0c;找到配置的站点&#xff0c;点击管理&#xff0c;找到伪静态 点击确认保存即可。 phpstudy会自动重启站点。 此时&#xff0c;…

Hive-技术补充-ANTLR词法语法分析

一、背景 要清晰的理解一条Hql是如何编译成MapReduce任务的&#xff0c;就必须要学习ANTLR。下面是ANTLR的官方网址&#xff0c;下面让我们一起来跟着官网学习吧 ANTLR 二、ANTLR元语言 1、启发 静下来想想&#xff0c;一门语言有什么组成&#xff0c;比如我们的中文&…

FPGA高端项目:FPGA基于GS2971+GS2972架构的SDI视频收发+GTX 8b/10b编解码SFP光口传输,提供2套工程源码和技术支持

目录 1、前言免责声明 2、相关方案推荐本博主所有FPGA工程项目-->汇总目录本博已有的 SDI 编解码方案本方案的SDI接收发送本方案的SDI接收图像缩放应用本方案的SDI接收纯verilog图像缩放纯verilog多路视频拼接应用本方案的SDI接收HLS图像缩放HLS多路视频拼接应用本方案的SDI…

基于Logstash的动态表同步方案

文章目录 引言I 动态表的同步1.1 利用数据库函数进行动态表名拼接1.2 利用shell脚步进行动态日期表名拼接1.3 方案小结II 增量同步III 同步多数据表引言 基于Logstash由SQLServer向Elasticsearch同步数据,兼容SQL Server 2005,在连接数据库时,url后面加上一个encrypt=false或…

【Mysql数据库基础02】单行函数、排序

单行函数、排序 1 单行函数1.1 常用函数1.1.1 length 字符串的长度1.1.2 ifnull 判断表达式是否为空 1.2 字符函数1.2.1 substr 提取自串1.2.2 转换大小写1.2.3 instr 返回起始索引1.2.4 trim 去除两端指定字符1.2.5 lpad 左填充指定长度 1.3 数学函数1.3.1 round 四舍五入1.3.…

Vue.js中使用Web Workers来创建一个秒表

在Vue.js中使用Web Workers来创建一个秒表应用可以提高性能&#xff0c;因为Web Workers可以在后台线程中运行&#xff0c;不阻塞主线程。下面是一个简单的Vue.js秒表应用的示例&#xff0c;该应用使用Web Worker来执行计时功能。 首先&#xff0c;我们创建一个Web Worker文件…

【iOS】——Blocks

文章目录 前言一、Blocks概要1.什么是Blocks 二、Block模式1.block语法2.block类型变量3.截获自动变量值4._Block修饰符5.截获的自动变量 三、Blocks的实现1.Block的实质2.截获自动变量值3._Block说明符4.Block存储域 前言 一、Blocks概要 1.什么是Blocks Blocks是C语言的扩…

国创证券|超五成私募看好AI成为年度行情主线

3月18日&#xff0c;2024年度全球游戏开发者大会&#xff08;GDC&#xff09;与英伟达GPU&#xff08;图形处理器&#xff09;技能大会&#xff08;GTC&#xff09;举行&#xff0c;出资者关于A股商场的“人工智能”也给予了更多等待。 2月6日至3月15日期间&#xff0c;商场对…

使用 nsenter 排查容器网络问题

需求 我想进入容器中执行 curl 命令探测某个地址的连通性&#xff0c;但是容器镜像里默认没有 curl 命令。我这里是一个内网环境不太方便使用 yum 或者 apt 安装&#xff0c;怎么办&#xff1f; 这个需求比较典型&#xff0c;这里教大家一个简单的方法&#xff0c;使用 nsent…

Linux/Bizness

Enumeration nmap 用 nmap 扫描了常见的端口&#xff0c;发现对外开放了22,80,443 ┌──(kali㉿kali)-[~] └─$ nmap 10.10.11.252 Starting Nmap 7.93 ( https://nmap.org ) at 2024-03-08 01:21 EST Nmap scan report for 10.10.11.252 Host is up (0.36s latency). Not…

H266开源视频编码器VVENC现状

VVenC 是由 Fraunhofer HHI 研究团队开发的&#xff0c;主要是视频编码系统组。HHI 是欧洲最大的研究组织 Fraunhofer 协会的成员&#xff0c;该协会是德国的一个大型非营利性组织。源代码在&#xff1a; https://github.com/fraunhoferhhi/vvenc VVenC几乎与H.266视频标准同时…

什么是VR虚拟现实防火体验馆|VR设备购买|元宇宙文旅

VR虚拟现实防火体验馆是利用虚拟现实&#xff08;VR&#xff09;技术打造的一个模拟火灾场景的体验空间。通过虚拟现实头盔和交互设备&#xff0c;参与者可以在虚拟环境中感受和学习如何正确面对火灾&#xff0c;并进行逃生和自救。 这种虚拟现实防火体验馆通常会模拟真实的火灾…

CPU生产的生命周期 - 回收篇

电子废物的可持续再利用和回收每年变得更加重要。随着技术在全世界范围内变得越来越普及和普及&#xff0c;电子垃圾的产生率也随之直接增加。计算机的心脏是中央处理单元或CPU。我们认为电子废物的许多产品都包含某种处理器。为了保护环境免受日益严重的电子废物堆积问题的影响…

在Termux中安装个人hexo博客并结合cpolar工具实现远程访问

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

fn键设置

1、起因&#xff0c;按F7 F8调试&#xff0c;总是执行Fn功能&#xff0c;让人反感。 2、搜索了下对应的型号Fn键设置 方法一&#xff1a;浮岛式键盘Fn热键切换功能 方法二&#xff1a;通过键盘属性设置Fn锁定功能。【方法一解决了我的问题&#xff0c;方法二不试了。】 3、问题…

Windows电脑上如何进行硬盘分区操作!

在Windows操作系统环境下,对电脑硬盘进行分区是一种常见的硬盘管理手段,它可以帮助用户更好地组织和管理存储空间,确保操作系统、应用程序和用户数据各有所属。本文将详细介绍在Windows PC上进行硬盘分区的步骤,适用于Windows 7到Windows 11等不同版本的操作系统。 步骤一:…

SinoDB数据库运行分析

SinoDB数据库运行主要从数据库互斥资源等待、数据库写类型、备份文件有效性、Chunk状态等15个方向进行分析&#xff0c;具体说明如下&#xff1a; 一、数据库互斥资源等待 检查项目 数据库互斥资源等待 检查命令 onstat -g con |head -20 说明 onstat -g con 查看目前数据处…

mysql的学习笔记

干前端好几年了,只会前端总感觉少了条腿,处处不自在,决定今年学习下后端的东西.以前总想着学node会更快,但是实际工作上却用不上. 出来混,总是要还的,该学的javaWeb这一套体系的东西,总是需要学习的. 那就开始啦. 一,在本地电脑mac上安装mysql 这个参考的这篇文章,照着做一次…

Python:filter过滤器

filter() 是 Python 中的一个内置函数&#xff0c;用于过滤序列&#xff0c;过滤掉不符合条件的元素&#xff0c;返回由符合条件元素组成的新列表。该函数接收两个参数&#xff0c;一个是函数&#xff0c;一个是序列&#xff0c;序列的每个元素作为参数传递给函数进行判定&…