Python 数据结构 4.单向链表

news2025/3/3 8:09:17

惟愿春日不迟,相逢终有时

                                            —— 25.3.2

一、单向链表的基本概念

1.单向链表的概念

        对于顺序存储的结构,最大的缺点就是:插入删除 的时候需要移动大量的元素,所以基于前人的智慧,他们发明了链表。

        链表是由一个个结点组成,每个结点之间通过链接关系串联起来,每个结点都有一个后继结点,最后一个结点的后继结点为空结点,如图所示:

        由链接关系 A ->B 组织起来的两个结点,B 被称为 A的后继结点,A 被称为 B 的前驱结点。链表分为:单向链表、双向链表、循环链表等等。

        一个链表结点由两部分组成:数据域指针域。数据可以是任意类型,由编码的人自行指定。指针域 指向 后继结点 的地址。一个结点包含的两部分,如下图所示:


2.单向链表的元素插入

        单向链表的元素插入,就是指给定一个索引 i 和一个元素 data,生成一个值为 data 的结点,并且插入到第i个位置上。

元素插入的步骤

第1步:判断插入位置是否合法,如果不合法则抛出异常(比如:原本只有5个元素,给定的索引是100,那显然这个位置是不合法的)

第2步:对给定的元素,生成一个链表结点。

第3步:如果插入位置是 0,则直接把生成的结点的后继结点,设置为当前的链表头结点,并且把生成的结点设置为新的链表头,

第4步:如果插入位置不是 0,则遍历到插入位置的前一个位置,把生成的结点插入进来

第5步:更新链表的大小,即对链表的元素执行加一操作。


3.单向链表的元素删除

        单向链表的元素删除,就是指给定一个索引 i,将从链表头开始数到的第 i 个结点删除。

元素删除的步骤

第1步:判断删除位置是否合法,如果不合法则抛出异常。

第2步:如果删除位置为首个结点,直接把链表头更新为它的后继结点,

第3步:如果删除位置非首个结点,则遍历到要删除位置的前一个结点,并且把前一个结点的后继结点设置为它后继的后继。

第4步:更新链表的大小,也就是将链表的大小执行减一操作。


4.单向链表的元素查找

        单向链表的元素查找,是指在链表中查找指定元素 x 是否存在,如果存在则返回该结点,否则返回 null。由于需要遍历整个链表进行元素对比,所以查找的时间复杂度为 (n)。

元素查找的步骤

第1步:遍历整个链表,对链表中的每个元素,和指定元素进行比较,如果相等则返回当前遍历到的结点;

第2步:如果遍历完整个链表,都没有找到相等的元素,则返回 NULL;


5.单向链表的元素索引

        单向链表的元素索引,是指给定一个索引值 i,从链表头结点开始数,数到第 i 个结点并且返回它,时间复杂度 O(n)。

元素索引的步骤

第1步:首先判断给定的索引是否合法,不合法就抛出异常

第2步:直接通过索引访问即可获得对应的元素;


6.单向链表的元素修改

        单向链表的元素修改是指将链表中指定索引的元素更新为新的值。

元素修改的步骤

第1步:直接通过索引访问即可获得对应的结点,修改成指定的值;


二、Python中的单向链表

class ListNode:
    def __init__(self, x):
        self.val = x
        self.next = None

class LinkedList:
    def __init__(self):
        self.head = None
        self.len = 0

    def size(self):
        return self.len

    def insert(self, pos, val):
        if pos < 0 or pos > self.len:
            raise ValueError("index out of range")
        new_node = ListNode(val)

        if pos == 0:
            new_node.next = self.head
            self.head = new_node
        else:
            prev = self.head
            for _ in range(pos - 1):
                prev = prev.next
            new_node.next = prev.next
            prev.next = new_node
        self.len += 1

    def delete(self, pos):
        if pos < 0 or pos >= self.size():
            raise ValueError("index out of range")

        if pos == 0:
            self.head = self.head.next
        else:
            prev = self.head
            for _ in range(pos - 1):
                prev = prev.next
            prev.next = prev.next.next
        self.len -= 1

    def update(self, pos, val):
        if pos < 0 or pos >= self.size():
            raise ValueError("index out of range")

        curr = self.head
        for _ in range(pos):
            curr = curr.next
        curr.val = val

    def search(self, val):
        curr = self.head
        while curr:
            if curr.val == val:
                return curr
            curr = curr.next
        return None

    def index(self, val):
        index = 0
        curr = self.head
        while curr:
            if curr.val == val:
                return index
            curr = curr.next
            index += 1
        return -1

    def print(self):
        curr = self.head
        while curr:
            print(curr.val, end=" -> ")
            curr = curr.next
        print(None)

def Test():
    list = LinkedList()
    list.insert(0, 1)
    list.print()
    list.insert(1, 1)
    list.print()
    list.insert(2, 4)
    list.print()
    list.insert(0, 1)
    list.print()
    list.insert(1, 1)
    list.print()
    list.insert(2, 4)
    list.print()
    list.update(2, 5)
    list.print()
    list.delete(2)
    list.print()
    list.insert(2, 4)
    list.print()
    list.update(4, 1)
    list.print()

    node = list.search(4)
    if node:
        print("Node found:", node.val)
    else:
        print("Node not found")

    x = list.index(4)
    print("Index of 4:", x)

    x = list.index(5)
    print("Index of 5:", x)

Test()


三、单向链表实战

1.面试题 02.02. 返回倒数第 k 个节点

实现一种算法,找出单向链表中倒数第 k 个节点。返回该节点的值。

注意:本题相对原题稍作改动

示例:

输入: 1->2->3->4->5 和 k = 2
输出: 4

说明:

给定的 k 保证是有效的。

快慢指针法

思路与算法

① 初始化两个指针fast 和 slow,它们都指向链表的头节点 head

② 移动快指针:让 fast 指针先向前移动 k 步。

③ 同步移动快慢指针:当 fast 指针移动到链表的末尾时,slow 指针正好指向倒数第 k 个节点。

④ 返回结果:返回 slow 指针所指向节点的值。

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def kthToLast(self, head: Optional[ListNode], k: int) -> int:
        fast = head
        slow = head
        while k > 0:
            fast = fast.next
            k -= 1
        while fast:
            fast = fast.next
            slow = slow.next
        return slow.val


2.83. 删除排序链表中的重复元素

给定一个已排序的链表的头 head , 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 。

示例 1:

输入:head = [1,1,2]
输出:[1,2]

示例 2:

输入:head = [1,1,2,3,3]
输出:[1,2,3]

提示:

  • 链表中节点数目在范围 [0, 300] 内
  • -100 <= Node.val <= 100
  • 题目数据保证链表已经按升序 排列 

方法一 双指针判断

思路与算法

① 初始化指针curr 指针从链表的头节点 head 开始。prev 指针初始化为 None,用于记录当前节点的前一个节点。

② 遍历链表:外层 while 循环用于遍历整个链表,直到 curr 为 None。内层 while 循环用于处理当前节点 curr 与前一个节点 prev 值相同的情况。如果发现重复,则将 prev.next 指向 curr.next,从而跳过重复的节点。

③ 更新指针:如果没有发现重复,则更新 prev 为当前节点 curr,并将 curr 移动到下一个节点。​

④ 返回结果:最终返回处理后的链表头节点 head

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]:
        curr = head
        prev = None
        while curr:
            while prev != None and curr.val == prev.val:
                prev.next = curr.next
                curr = prev.next
                if curr == None:
                    break
            if curr == None:
                break
            prev = curr
            curr = curr.next
        return head


方法二 一次遍历进行判断

思路与算法

① ​初始化curr指针指向链表的头节点head

② 空链表处理:如果链表为空(curr == None),直接返回head

​③ 遍历链表:如果当前节点的值curr.val与下一个节点的值curr.next.val相同,则通过curr.next = curr.next.next删除下一个节点。如果值不相同,则移动curr指针到下一个节点。

④ 返回结果:最终返回处理后的链表头节点head

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]:
        curr = head
        if curr == None:
            return head
        while curr.next:
            if curr.val == curr.next.val:
                curr.next = curr.next.next
            else:
                curr = curr.next
        return head


3.面试题 02.01. 移除重复节点

编写代码,移除未排序链表中的重复节点。保留最开始出现的节点。

示例1:

 输入:[1, 2, 3, 3, 2, 1]
 输出:[1, 2, 3]

示例2:

 输入:[1, 1, 1, 1, 2]
 输出:[1, 2]

提示:

  1. 链表长度在[0, 20000]范围内。
  2. 链表元素在[0, 20000]范围内。

方法一、哈希表标记法

思路与算法

① 哈希表标记法​:利用数组模拟哈希表(大小20001),用于记录节点值是否已存在。数组下标对应节点值,元素值为1表示该值已出现过

② 双指针遍历:

        tmp指针:指向当前已去重链表的尾节点,用于连接新节点。

   curr指针:遍历原链表,检查当前节点是否重复。

③ 边界处理:​若链表为空直接返回;初始化时先将头节点加入哈希表,避免后续判空操作

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def removeDuplicateNodes(self, head: Optional[ListNode]) -> Optional[ListNode]:
        if head == None:
            return None
        # 哈希表
        hash = [0 for i in range(20001)]
        tmp = head
        curr = head.next
        hash[head.val] = 1
        while curr:
            if hash[curr.val] == 0:
                hash[curr.val] = 1
                tmp.next = curr
                tmp = tmp.next
            curr = curr.next
        tmp.next = None
        return head


方法二、字典

思路与算法

初始化字典:首先,代码创建了一个空字典 dict,用于存储已经出现过的节点值。如果链表为空(head == None),则直接返回 None,因为空链表不需要去重。​

② 处理头节点:将头节点的值 head.val 存入字典,表示该值已经出现过。

③ 遍历链表:使用 curr 指针遍历链表,初始时 curr 指向头节点。在遍历过程中,检查 curr.next 的值是否已经存在于字典中:如果 curr.next.val 不在字典中,说明该值尚未出现过,将其存入字典,并将 curr 指针移动到下一个节点。如果 curr.next.val 已经在字典中,说明该值是重复的,跳过该节点,即将 curr.next 指向 curr.next.next,从而删除重复节点。

④ 返回结果:遍历结束后,返回去重后的链表头节点 head

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def removeDuplicateNodes(self, head: Optional[ListNode]) -> Optional[ListNode]:
        dict = {}
        if head == None:
            return None
        dict[head.val] = 1
        curr = head
        while curr.next:
            if curr.next.val not in dict:
                dict[curr.next.val] = 1
                curr = curr.next
            else:
                curr.next = curr.next.next  
        return head


四、单向链表的应用

链表相比于顺序表的优点在于:对于给定的结点,删除操作优于顺序表

MMO游戏开发 —— AOI(Area of Interest)

简单来说,每个玩家只关心他周围的玩家的数据同步,而不关心整个世界的数据,有一种经典的实现方式:双向十字链表

所有玩家被串联在一个十字链表上,玩家移动其实就是链表上节点交换位置的过程,每个玩家想获取其他玩家的数据,只需要在十字链表上进行遍历即可,而服务器同步给客户端的数据,受AOI控制,所以可以根据客户端实际能够承受的性能,调整AOI的半径

通过有效的实现AOI技术,游戏开发中可以:

① 减少服务器负载 ② 降低网络延迟 ③ 提升游戏性能 ④ 增强玩家用户体验

 

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

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

相关文章

LeeCode题库第四十题

40.组合总和II 项目场景&#xff1a; 给定一个候选人编号的集合 candidates 和一个目标数 target &#xff0c;找出 candidates 中所有可以使数字和为 target 的组合。 candidates 中的每个数字在每个组合中只能使用 一次 。 注意&#xff1a;解集不能包含重复的组合。 示…

玩转大模型——deepseek本地部署与ollama 非C盘安装之ChatBox配置

文章目录 ollama安装ollama是什么DeepSeek是什么下载地址非C盘安装配置大模型目录大模型下载安装deepseek-r1:1.5b安装deepseek-r1:7b ChatBox安装参考资料 ollama安装 ollama是什么 Ollama 是一个专注于本地运行大型语言模型的工具。它允许用户在本地环境中部署和运行各种开…

DAV_postgresql_3-schema

schem介绍&#xff1a; 什么是schema? 用户对象的集合叫做模式 不同模式下的对象可以同名 可以把用户下对象根据业务分类&#xff0c;不同的对象放在不同的模式 一个用户可以创与拥有多个模式 一个模式只能属于一个用户 普通用户创建模式需要授权指定数据库下的创建权限…

Hive-04之存储格式、SerDe、企业级调优

一、主题 hive表的数据压缩和文件存储格式hive的自定义UDF函数hive的JDBC代码操作hive的SerDe介绍和使用hive的优化 二、要点 1. hive表的文件存储格式 Hive支持的存储数的格式主要有&#xff1a;TEXTFILE&#xff08;行式存储&#xff09; 、SEQUENCEFILE(行式存储)、ORC&…

信号和槽

connect(信号发送者&#xff0c;发送的信号&#xff0c;信号接收者&#xff0c;信号的处理); 信号函数和槽函数的参数必须是一样的&#xff0c;但信号的参数可以多余槽函数的参数&#xff08;前面的参数类型必须一致&#xff09; 是控件和控件间的信号传递&#xff0c;这两个…

从零开始用react + tailwindcss + express + mongodb实现一个聊天程序(八) 聊天框用户列表

简单画了个聊天框 就是咱们的HomePage.jsx 1.后端接口开发 在server/src/index.js 新增 messagesRoutes 先引入 import messageRoutes from ./routes/message.route.js // 消息接口 app.use(/api/messages, messageRoutes) 在routes文件夹下新建message.route.js 有3个路…

关于后端使用Boolean或boolean时前端收到的参数的区别

当后端使用的是Boolean时&#xff0c;调用的方法是setIsLoginUser&#xff0c;前端收到的参数的参数名是isLoginUser 而当后端使用的是boolean时&#xff0c;调用的方法是setLoginUser&#xff0c;前端收到的参数的参数名是loginUser 封装类和基本数据类型在使用时需要注意这…

智能称重搬物寻迹小车(论文+源码)

1 系统设计方案确定 本次设计的总系统有以下几个模块分别是避障模块&#xff0c;循迹模块&#xff0c;二维码扫描电路&#xff0c;称重电路&#xff0c;LCD显示电路和电机驱动模块&#xff0c;而且这几个模块都是由单片机stm32控制的&#xff0c;整个系统的框图如下图所示。其…

使用 ASP.NET Core 创建和下载 zip 文件

对于最近的一个功能&#xff0c;我必须从用 ASP.NET Core 编写的内部网站下载一批文件。在下载文件之前对其进行压缩&#xff0c;结果证明这是一种轻松实现多文件下载的好方法。.NET 提供了所有需要的功能&#xff0c;在本文中&#xff0c;我将向您展示如何实现它。 首先&#…

dify绑定飞书多维表格

dify 绑定飞书和绑定 notion 有差不多的过程&#xff0c;都需要套一层应用的壳子&#xff0c;而没有直接可以访问飞书文档的 API。本文记录如何在dify工具中使用新增多条记录工具。 创建飞书应用 在飞书开放平台创建一个应用&#xff0c;个人用户创建企业自建应用。 自定义应…

SQL server配置ODBC数据源(本地和服务器)

本地配置 1. 控制面板中找到系统ODBC数据源&#xff08;打开控制面板直接搜&#xff09; 2. 选择“系统DSN”&#xff0c;点击“添加” 3. 选择“SQL server” 4. 名称和描述自己填&#xff0c;服务器选择本机设备名称 5. 选择ID和密码验证&#xff0c;并填写本地SQL server登…

LogiSim教程

一、LogiSim是什么 Logisim是一种设计数字电路的工具。 二、安装LogiSim 下载地址 https://sourceforge.net/projects/circuit/ 此软件需要java运行环境。 三、使用LogiSim &#xff08;一&#xff09;界面 Logisim界面分为菜单栏、工具栏、资源管理器&#xff0c;属性表…

RAP: Efficient Text-Video Retrieval with Sparse-and-Correlated Adapter

​​标题&#xff1a;RAP:基于稀疏相关适配器的高效文本视频检索 原文链接&#xff1a;RAP: Efficient Text-Video Retrieval with Sparse-and-Correlated Adapter - ACL Anthology 发表&#xff1a;ACL-2024(NLP领域CCF A类) 摘要 文本-视频检索&#xff08;TVR&#xff0…

I2C驱动(十一) -- gpio模拟的i2c总线驱动i2c-gpio.c分析

相关文章 I2C驱动(一) – I2C协议 I2C驱动(二) – SMBus协议 I2C驱动(三) – 驱动中的几个重要结构 I2C驱动(四) – I2C-Tools介绍 I2C驱动(五) – 通用驱动i2c-dev.c分析 I2C驱动(六) – I2C驱动程序模型 I2C驱动(七) – 编写I2C设备驱动之i2c_driver I2C驱动(八) – 编写I2C…

不要升级,Flutter Debug 在 iOS 18.4 beta 无法运行,提示 mprotect failed: Permission denied

近期如果有开发者的 iOS 真机升级到 18.4 beta&#xff0c;大概率会发现在 debug 运行时会有 Permission denied 的相关错误提示&#xff0c;其实从 log 可以很直观看出来&#xff0c;就是 Dart VM 在初始化时&#xff0c;对内核文件「解释运行&#xff08;JIT&#xff09;」时…

私有化部署大模型推理性能分析

从用户感知角度分析私有化部署的大模型推理性能&#xff0c;这里的用户感知包括响应速度、生成速度、系统可用性以及系统稳定性。大模型首先获取输入内容的字符串&#xff0c;将这部分内容转换为模型token,过模型推理,到最后输出第一个token的时间是ttft&#xff0c;从这以后&a…

EtherCAT总线绝对值伺服如何使用

EtherCAT总线掉线如何自动重启。 EtherCAT总线掉线如何自动重启_ethercat从站断线-CSDN博客文章浏览阅读1.2k次。本文介绍了在EtherCAT通信中,当从站出现掉线情况时,如何通过设置自动重启功能来解决这一问题。详细步骤包括在CODESYS环境中启用从站的自动重启选项。https://r…

可商用街头文化艺术海报封面手写涂鸦标题LOGO排版英文字体 FS163 TYPE FACE

Freestyle 163 &#xff08;FS163&#xff09;是一个受街头文化和城市艺术启发的视觉宣言。该字体旨在突出我们的文化和创意根源&#xff0c;反映了街头运动、城市艺术以及来自社会和边缘的故事。 FS163与面临挑战、质疑规范、放大被忽视声音的品牌和个人联系在一起&#xff0c…

使用3090显卡部署Wan2.1生成视频

layout: post title: 使用3090显卡部署Wan2.1生成视频 catalog: true tag: [Kubernetes, GPU, AI] 使用3090显卡部署Wan2.1生成视频 1. 环境说明2. 模型下载3. 克隆仓库4. 安装依赖5. 生成视频 5.1. 使用generate脚本生成5.2. 使用gradio启动UI界面生成 5.2.1. 启动gradio服务5…

Diffusion——扩散模型(未完待续)

论文链接&#xff1a;https://arxiv.org/abs/2006.11239 简介 扩散模型&#xff08;Diffusion Model&#xff09;是用于生成数据的一类深度生成模型&#xff0c;特别擅长于图像生成。其工作原理基于通过随机噪声的逐步转换来生成目标数据。扩散模型分为两部分&#xff1a;正向…