玩转python: 掌握Python数据结构之链表
链表是计算机科学中最基础的数据结构之一,也是许多高级数据结构和算法的基础。本文将带你从零开始,逐步掌握链表的概念、实现和应用。通过丰富的案例和通俗易懂的解释,你将能够轻松理解并应用链表。
什么是链表?
链表是一种线性数据结构,由一系列节点(Node)组成。每个节点包含两个部分:数据域和指针域。数据域用于存储数据,指针域用于指向下一个节点。链表中的节点通过指针连接在一起,形成一个链式结构。
与数组不同,链表的内存空间不需要连续分配。这意味着链表可以更灵活地利用内存空间,但也意味着访问链表中的元素需要从头节点开始逐个遍历。
链表的类型
链表有多种类型,常见的有:
- 单链表(Singly Linked List):每个节点只有一个指针,指向下一个节点。
- 双链表(Doubly Linked List):每个节点有两个指针,一个指向前一个节点,一个指向下一个节点。
- 循环链表(Circular Linked List):尾节点的指针指向头节点,形成一个环。
本文将重点介绍单链表,因为它是最基础且最常用的链表类型。
单链表的实现
在Python中,我们可以通过定义一个Node类和一个LinkedList类来实现单链表。
1. 定义节点类
首先,我们需要定义一个节点类Node,它包含数据域和指针域。
class Node:def __init__(self, data):self.data = data # 数据域self.next = None # 指针域,初始化为None
2. 定义链表类
接下来,我们定义一个链表类LinkedList,它包含对链表的各种操作,如插入、删除、遍历等。
class LinkedList:def __init__(self):self.head = None # 头节点,初始化为None# 在链表末尾插入节点def append(self, data):new_node = Node(data)if self.head is None: # 如果链表为空,新节点为头节点self.head = new_nodereturnlast_node = self.headwhile last_node.next: # 遍历到链表末尾last_node = last_node.nextlast_node.next = new_node # 将新节点插入到末尾# 在链表头部插入节点def prepend(self, data):new_node = Node(data)new_node.next = self.head # 新节点的指针指向当前头节点self.head = new_node # 更新头节点为新节点# 删除指定数据的节点def delete(self, data):current_node = self.headif current_node and current_node.data == data: # 如果头节点就是要删除的节点self.head = current_node.nextcurrent_node = Nonereturnprev_node = Nonewhile current_node and current_node.data != data: # 遍历查找要删除的节点prev_node = current_nodecurrent_node = current_node.nextif current_node is None: # 如果没找到要删除的节点returnprev_node.next = current_node.next # 跳过要删除的节点current_node = None# 打印链表def print_list(self):current_node = self.headwhile current_node:print(current_node.data, end=" -> ")current_node = current_node.nextprint("None")
3. 使用链表
现在,我们可以使用LinkedList类来创建和操作链表。
# 创建一个链表
llist = LinkedList()# 在链表末尾插入节点
llist.append(1)
llist.append(2)
llist.append(3)# 在链表头部插入节点
llist.prepend(0)# 打印链表
llist.print_list() # 输出: 0 -> 1 -> 2 -> 3 -> None# 删除节点
llist.delete(2)# 打印链表
llist.print_list() # 输出: 0 -> 1 -> 3 -> None
链表的应用案例
链表在实际应用中有很多用途,以下是几个常见的案例:
1. 实现队列
队列是一种先进先出(FIFO)的数据结构,链表可以很方便地实现队列。
class Queue:def __init__(self):self.linked_list = LinkedList()# 入队def enqueue(self, data):self.linked_list.append(data)# 出队def dequeue(self):if self.linked_list.head is None:return Nonedata = self.linked_list.head.dataself.linked_list.delete(data)return data# 打印队列def print_queue(self):self.linked_list.print_list()# 使用队列
queue = Queue()
queue.enqueue(1)
queue.enqueue(2)
queue.enqueue(3)
queue.print_queue() # 输出: 1 -> 2 -> 3 -> None
print(queue.dequeue()) # 输出: 1
queue.print_queue() # 输出: 2 -> 3 -> None
2. 实现栈
栈是一种后进先出(LIFO)的数据结构,链表也可以很方便地实现栈。
class Stack:def __init__(self):self.linked_list = LinkedList()# 入栈def push(self, data):self.linked_list.prepend(data)# 出栈def pop(self):if self.linked_list.head is None:return Nonedata = self.linked_list.head.dataself.linked_list.delete(data)return data# 打印栈def print_stack(self):self.linked_list.print_list()# 使用栈
stack = Stack()
stack.push(1)
stack.push(2)
stack.push(3)
stack.print_stack() # 输出: 3 -> 2 -> 1 -> None
print(stack.pop()) # 输出: 3
stack.print_stack() # 输出: 2 -> 1 -> None
3. 反转链表
反转链表是一个经典的面试题,我们可以通过修改指针的方向来实现。
def reverse_linked_list(llist):prev_node = Nonecurrent_node = llist.headwhile current_node:next_node = current_node.next # 保存下一个节点current_node.next = prev_node # 反转指针prev_node = current_node # 移动prev_nodecurrent_node = next_node # 移动current_nodellist.head = prev_node # 更新头节点# 反转链表
llist = LinkedList()
llist.append(1)
llist.append(2)
llist.append(3)
llist.print_list() # 输出: 1 -> 2 -> 3 -> None
reverse_linked_list(llist)
llist.print_list() # 输出: 3 -> 2 -> 1 -> None
4. 链表实现LRU缓存
LRU(Least Recently Used)缓存是一种常见的缓存淘汰策略,它会优先淘汰最近最少使用的数据。链表可以很好地支持LRU缓存的实现。
案例:浏览器缓存
假设你正在浏览网页,浏览器会将你最近访问的网页缓存起来。当你访问一个新网页时,如果缓存已满,浏览器会淘汰最久未访问的网页缓存。这个过程可以用链表来实现。
class LRUCache:def __init__(self, capacity):self.capacity = capacity # 缓存容量self.cache = {} # 存储缓存数据self.linked_list = LinkedList() # 使用链表记录访问顺序# 获取缓存数据def get(self, key):if key in self.cache:# 如果缓存中有该数据,将其移到链表头部(表示最近使用)self.linked_list.delete(key)self.linked_list.prepend(key)return self.cache[key]return -1 # 如果缓存中没有该数据,返回-1# 插入缓存数据def put(self, key, value):if key in self.cache:# 如果缓存中已有该数据,更新其值并移到链表头部self.linked_list.delete(key)elif len(self.cache) >= self.capacity:# 如果缓存已满,淘汰链表尾部的数据(最久未使用)last_key = self.linked_list.headwhile last_key.next:last_key = last_key.nextself.linked_list.delete(last_key.data)del self.cache[last_key.data]# 将新数据插入链表头部self.linked_list.prepend(key)self.cache[key] = value# 使用LRU缓存
cache = LRUCache(2)
cache.put(1, "网页1")
cache.put(2, "网页2")
print(cache.get(1)) # 输出: 网页1
cache.put(3, "网页3") # 缓存已满,淘汰网页2
print(cache.get(2)) # 输出: -1(网页2已被淘汰)
通俗解释:
- 链表记录了数据的访问顺序,最近访问的数据在链表头部,最久未访问的数据在链表尾部。
- 当缓存满时,直接淘汰链表尾部的数据,确保缓存中始终保留最近使用的数据。
5. 链表实现多项式相加
多项式相加是数学中常见的操作,链表可以用来表示多项式,并实现相加功能。
案例:多项式计算
假设有两个多项式:
- 多项式A:
3x^2 + 2x + 5 - 多项式B:
4x^3 + 2x^2 + 1
我们可以用链表表示这两个多项式,并实现它们的相加。
class PolynomialTerm:def __init__(self, coefficient, exponent):self.coefficient = coefficient # 系数self.exponent = exponent # 指数self.next = None # 指向下一个项的指针class Polynomial:def __init__(self):self.head = None # 多项式的头节点# 添加项def add_term(self, coefficient, exponent):new_term = PolynomialTerm(coefficient, exponent)if self.head is None:self.head = new_termelse:current = self.headwhile current.next:current = current.nextcurrent.next = new_term# 打印多项式def print_polynomial(self):current = self.headwhile current:print(f"{current.coefficient}x^{current.exponent}", end=" + " if current.next else "\n")current = current.next# 多项式相加def add_polynomials(poly1, poly2):result = Polynomial()current1 = poly1.headcurrent2 = poly2.headwhile current1 and current2:if current1.exponent > current2.exponent:result.add_term(current1.coefficient, current1.exponent)current1 = current1.nextelif current1.exponent < current2.exponent:result.add_term(current2.coefficient, current2.exponent)current2 = current2.nextelse:result.add_term(current1.coefficient + current2.coefficient, current1.exponent)current1 = current1.nextcurrent2 = current2.next# 将剩余项加入结果while current1:result.add_term(current1.coefficient, current1.exponent)current1 = current1.nextwhile current2:result.add_term(current2.coefficient, current2.exponent)current2 = current2.nextreturn result# 创建多项式A:3x^2 + 2x + 5
polyA = Polynomial()
polyA.add_term(3, 2)
polyA.add_term(2, 1)
polyA.add_term(5, 0)# 创建多项式B:4x^3 + 2x^2 + 1
polyB = Polynomial()
polyB.add_term(4, 3)
polyB.add_term(2, 2)
polyB.add_term(1, 0)# 相加并打印结果
result = Polynomial.add_polynomials(polyA, polyB)
result.print_polynomial() # 输出: 4x^3 + 5x^2 + 2x + 6
通俗解释:
- 每个链表节点表示多项式的一个项,包含系数和指数。
- 通过遍历两个链表,将相同指数的项相加,最终得到一个新的多项式。
6. 链表实现约瑟夫问题
约瑟夫问题是一个经典的数学问题,描述如下:n个人围成一圈,从第k个人开始报数,数到m的人出列,直到所有人出列。链表可以很好地模拟这个过程。
案例:游戏中的淘汰机制
假设你和朋友们围成一圈玩“数到3就淘汰”的游戏,链表可以模拟这个过程。
def josephus(n, k, m):# 创建循环链表class Node:def __init__(self, data):self.data = dataself.next = Nonehead = Node(1)prev = headfor i in range(2, n + 1):new_node = Node(i)prev.next = new_nodeprev = new_nodeprev.next = head # 形成环# 找到第k个人current = headfor _ in range(k - 1):current = current.next# 开始报数并淘汰while current.next != current:# 报数到m-1for _ in range(m - 1):current = current.next# 淘汰第m个人print(f"淘汰:{current.next.data}")current.next = current.next.nextcurrent = current.nextprint(f"最后剩下:{current.data}")# 使用约瑟夫问题
josephus(5, 1, 3) # 输出: 淘汰:3 -> 淘汰:1 -> 淘汰:5 -> 淘汰:2 -> 最后剩下:4
通俗解释:
- 链表模拟了一个圆圈,每个人是一个节点,节点的指针指向下一个人。
- 从第
k个人开始报数,数到m的人被淘汰(从链表中移除),直到最后剩下一个人。
总结
通过以上案例,我们可以看到链表在实际问题中的广泛应用。无论是缓存管理、数学计算还是游戏模拟,链表都能提供高效的解决方案。希望这些案例能帮助你更好地理解链表的应用场景,并激发你进一步探索数据结构的兴趣!
相关文章:
玩转python: 掌握Python数据结构之链表
链表是计算机科学中最基础的数据结构之一,也是许多高级数据结构和算法的基础。本文将带你从零开始,逐步掌握链表的概念、实现和应用。通过丰富的案例和通俗易懂的解释,你将能够轻松理解并应用链表。 什么是链表? 链表是一种线性…...
upload-labs详解(1-12)文件上传分析
目录 uploa-labs-main upload-labs-main第一关 前端防御 绕过前端防御 禁用js Burpsuite抓包改包 upload-labs-main第二关 上传测试 错误类型 upload-labs-env upload-labs-env第三关 上传测试 查看源码 解决方法 重命名,上传 upload-labs-env第四关…...
RAG系统(检索增强生成)的优化策略
RAG(检索增强生成)系统的优化可以从多个方面入手,主要包括数据、查询、检索、生成、框架和评估等几个重要环节。本文将详细介绍这些优化策略,并为每个环节提供具体的操作方法。 一、数据优化 1. 数据清洗和增强 数据质量直接影响检索和生成的效果,因此需要进行细致的数据…...
写毕业论文用哪个AI好?这6款AIGC论文工具给你答案
撰写毕业论文是一项艰巨的任务,AIGC 论文工具的出现为同学们提供了有力支持。以下 6 款工具在功能、适用场景等方面各有优势,助你高效完成毕业论文。 文赋 AI 论文 文赋 AI 论文堪称毕业论文写作的得力助手。它的生成速度令人惊叹,短短 5 分…...
loadingcache优化
问题分析 通过当前现场的火焰图进行分析 原本的loadingcache public LoadingCache<Integer, Student> map Caffeine.newBuilder().refreshAfterWrite(CONTRACT_CACHE_HOURS, TimeUnit.HOURS).maximumSize(CONTRACT_CONFIG_CACHE_SIZE).recordStats().build(key -> …...
【Vue3 Element UI - Plus + Tyscript 实现Tags标签输入及回显】
Vue3 Element Plus TypeScript 实现 Tags 标签输入及回显 在开发后台管理系统或表单页面时,动态标签(Tags) 是一个常见的功能需求。用户可以通过输入框添加标签,并通过关闭按钮删除标签,同时还需要支持标签数据的提…...
STM32 子设备通过CAN发送数据到主设备
采集ADC、GPS经纬坐标、温湿度数据、大气压数据通过CAN方式发送给主设备端,帧ID按照如下定义: 我尼玛一个标准帧ID位数据是11位,扩展帧才是111829位,它说最开头的是四位是真类型,并给我如下解释: 它把帧的定…...
Python可视化——地理空间型图表(自用)
地图信息可视化的实现就是将不可展开的曲面上的地理坐标信息转化为二维平面进行显示,这个过程也叫地图投影(空间三维投影到平面二维) 地图投影的要求:等面积、等角度、等距离。总的来说就是映射到二维平面中的任何点通过比例尺放大…...
WordPress报502错误问题解决-php-fpm-84.service loaded failed failed LSB: starts php-fpm
文章目录 问题描述问题排查问题解决 问题描述 服务器环境: php:8.4MySQL:8.0Nginx:1.26.2 在访问站点时,一直报502,而两天前还能正常访问。 问题排查 导致502的问题很多,比如站点访问量太大…...
Python在SEO中的自动化应用爬虫开发与日志分析实例
引言 搜索引擎优化(SEO)是数字营销中至关重要的一环,旨在提高网站在搜索引擎结果页面(SERP)中的排名。随着互联网数据的爆炸式增长,手动进行SEO分析和管理变得愈发困难。Python作为一种强大的编程语言&…...
thingsboard edge 在windows 环境下的配置
按照官方文档:Installing ThingsBoard Edge on Windows | ThingsBoard Edge,配置好java环境和PostgreSQL。 下载对应的windows 环境下的tb-edge安装包。下载附件 接下来操作具体如下 步骤1,需要先在thingsboard 服务上开启edge 权限 步骤2…...
nnMamba:基于状态空间模型的3D生物医学图像分割、分类和地标检测
摘要 本文提出了一种基于状态空间模型(SSMs)的创新架构——nnMamba,用于解决3D生物医学图像分割、分类及地标检测任务中的长距离依赖建模难题。nnMamba结合了卷积神经网络(CNN)的局部特征提取能力与SSMs的全局上下文建…...
nginx 配置403页面(已亲测)
问题:GET请求访问漏洞url即可看到泄露的内网ip 解决方式: 1.配置nginx 不显示真实Ip 2.限制接口只能是POST请求 具体配置: 编写一个403.html 在nginx的配置文件中,配置location参数: location /api/validationCode…...
SyntaxError: Invalid or unexpected token in JSON at position x
🤍 前端开发工程师、技术日更博主、已过CET6 🍨 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 🕠 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》、《前端求职突破计划》 🍚 蓝桥云课签约作者、…...
Uncaught TypeError: Cannot read properties of undefined (reading ‘xxx‘)
🤍 前端开发工程师、技术日更博主、已过CET6 🍨 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 🕠 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》、《前端求职突破计划》 🍚 蓝桥云课签约作者、…...
Nginx 跨域配置详细讲解
一、跨域请求概述 跨域资源共享(CORS,Cross-Origin Resource Sharing)是一种机制,它使用额外的HTTP头部来告诉浏览器让运行在一个origin(域)上的Web应用被准许访问来自不同源服务器上的指定的资源。当一个资…...
前端开发基石:HTML语义化深度解析与实践指南
一、语义化设计的本质价值 1.1 从文档结构到信息表达 在Web诞生初期(1991年),HTML仅包含18个标签用于学术文档展示。经过30年发展,HTML5已拥有超过110个标签,其中语义化标签占比提升至60%。这种演进背后是互联网从简…...
mongodb安装教程以及mongodb的使用
MongoDB是由C语言编写的一种面向文档的NoSQL数据库,旨在为WEB应用提供可扩展的高性能数据存储解决方案。与传统的关系型数据库(如 MySQL 或 PostgreSQL)不同,MongoDB 存储数据的方式是以 BSON(类似于 JSON 的二进制格式…...
C# 中的多线程同步机制:lock、Monitor 和 Mutex 用法详解
在多线程编程中,线程同步是确保多个线程安全地访问共享资源的关键技术。C# 提供了几种常用的同步机制,其中 lock、Monitor 和 Mutex 是最常用的同步工具。本文将全面介绍这三种同步机制的用法、优缺点以及适用场景,帮助开发者在多线程开发中做…...
【通义万相】蓝耘智算 | 开源视频生成新纪元:通义万相2.1模型部署与测评
【作者主页】Francek Chen 【专栏介绍】 ⌈ ⌈ ⌈人工智能与大模型应用 ⌋ ⌋ ⌋ 人工智能(AI)通过算法模拟人类智能,利用机器学习、深度学习等技术驱动医疗、金融等领域的智能化。大模型是千亿参数的深度神经网络(如ChatGPT&…...
stm32G473的flash模式是单bank还是双bank?
今天突然有人stm32G473的flash模式是单bank还是双bank?由于时间太久,我真忘记了。搜搜发现,还真有人和我一样。见下面的链接:https://shequ.stmicroelectronics.cn/forum.php?modviewthread&tid644563 根据STM32G4系列参考手…...
ES6从入门到精通:前言
ES6简介 ES6(ECMAScript 2015)是JavaScript语言的重大更新,引入了许多新特性,包括语法糖、新数据类型、模块化支持等,显著提升了开发效率和代码可维护性。 核心知识点概览 变量声明 let 和 const 取代 var…...
盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来
一、破局:PCB行业的时代之问 在数字经济蓬勃发展的浪潮中,PCB(印制电路板)作为 “电子产品之母”,其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透,PCB行业面临着前所未有的挑战与机遇。产品迭代…...
Vue3 + Element Plus + TypeScript中el-transfer穿梭框组件使用详解及示例
使用详解 Element Plus 的 el-transfer 组件是一个强大的穿梭框组件,常用于在两个集合之间进行数据转移,如权限分配、数据选择等场景。下面我将详细介绍其用法并提供一个完整示例。 核心特性与用法 基本属性 v-model:绑定右侧列表的值&…...
渲染学进阶内容——模型
最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...
剑指offer20_链表中环的入口节点
链表中环的入口节点 给定一个链表,若其中包含环,则输出环的入口节点。 若其中不包含环,则输出null。 数据范围 节点 val 值取值范围 [ 1 , 1000 ] [1,1000] [1,1000]。 节点 val 值各不相同。 链表长度 [ 0 , 500 ] [0,500] [0,500]。 …...
苍穹外卖--缓存菜品
1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据,减少数据库查询操作。 缓存逻辑分析: ①每个分类下的菜品保持一份缓存数据…...
Psychopy音频的使用
Psychopy音频的使用 本文主要解决以下问题: 指定音频引擎与设备;播放音频文件 本文所使用的环境: Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别 直接训练提示词嵌入向量的核心区别 您提到的代码: prompt_embedding = initial_embedding.clone().requires_grad_(True) optimizer = torch.optim.Adam([prompt_embedding...
Spring数据访问模块设计
前面我们已经完成了IoC和web模块的设计,聪明的码友立马就知道了,该到数据访问模块了,要不就这俩玩个6啊,查库势在必行,至此,它来了。 一、核心设计理念 1、痛点在哪 应用离不开数据(数据库、No…...
