当前位置: 首页 > news >正文

前端面试专栏-算法篇:21. 链表、栈、队列的实现与应用

🔥 欢迎来到前端面试通关指南专栏!从js精讲到框架到实战,渐进系统化学习,坚持解锁新技能,祝你轻松拿下心仪offer。
前端面试通关指南专栏主页
前端面试专栏规划详情在这里插入图片描述

链表、栈、队列的实现与应用

在计算机科学中,数据结构是组织和存储数据的方式,直接影响算法的效率和程序的性能。链表、栈和队列作为三种基础且重要的数据结构,广泛应用于各种软件系统中。本文将深入探讨它们的实现原理、操作方法及典型应用场景,帮助读者建立清晰的概念体系和实践能力。

一、链表(Linked List)

1.1 基本概念

链表是一种线性数据结构,由一系列节点(Node)组成。与数组不同,链表在内存中的存储是非连续的。每个节点包含两部分:

  • 数据域(Data):存储实际的数据元素
  • 指针域(Next):存储指向下一个节点的地址

链表的主要特点是:

  1. 动态存储:无需预先分配固定空间,可以实时申请内存
  2. 非连续内存:节点可以分散存储在内存各处
  3. 高效插入/删除:时间复杂度为O(1)(已知前驱节点时)
链表类型详解:
  1. 单链表(Singly Linked List)

    • 结构:每个节点包含数据和指向下一个节点的指针
    • 示例:节点A(data=5, next)→节点B(data=10, next)→节点C(data=15, next=null)
    • 应用:实现栈、队列等基础数据结构
  2. 双向链表(Doubly Linked List)

    • 结构:每个节点包含两个指针,分别指向直接前驱和直接后继
    • 示例:null←节点A(data=5)→←节点B(data=10)→←节点C(data=15)→null
    • 优势:可以双向遍历,删除操作更高效
    • 应用:浏览器历史记录、撤销操作功能
  3. 循环链表(Circular Linked List)

    • 单循环链表:尾节点指向头节点
    • 双循环链表:头节点的prev指向尾节点,尾节点的next指向头节点
    • 示例:A→B→C→A(循环)
    • 应用:轮播图、循环队列实现
复杂度对比:
操作单链表双向链表
访问O(n)O(n)
插入(头部)O(1)O(1)
删除(尾部)O(n)O(1)
内存占用较小较大

典型应用场景:

  • 操作系统中的进程调度(使用双向链表)
  • LRU缓存淘汰算法(双向链表+哈希表)
  • 多项式运算(使用链表存储系数和指数)

1.2 单链表的实现

以下是单链表的Python实现,包含节点类和链表类:

class Node:def __init__(self, data=None):self.data = dataself.next = Noneclass LinkedList:def __init__(self):self.head = None  # 头指针,指向第一个节点def is_empty(self):"""判断链表是否为空"""return self.head is Nonedef append(self, data):"""在链表尾部添加新节点"""new_node = Node(data)if self.is_empty():self.head = new_nodereturncurrent = self.headwhile current.next:current = current.nextcurrent.next = new_nodedef prepend(self, data):"""在链表头部插入新节点"""new_node = Node(data)new_node.next = self.headself.head = new_nodedef delete(self, key):"""删除第一个值为key的节点"""current = self.headprev = Nonewhile current and current.data != key:prev = currentcurrent = current.nextif current is None:  # 未找到keyreturnif prev is None:  # 删除的是头节点self.head = current.nextelse:prev.next = current.nextdef search(self, key):"""查找值为key的节点是否存在"""current = self.headwhile current and current.data != key:current = current.nextreturn current is not None  # 找到返回True,否则Falsedef display(self):"""显示链表中的所有元素"""elements = []current = self.headwhile current:elements.append(str(current.data))current = current.nextprint(" -> ".join(elements))# 使用示例
my_list = LinkedList()
my_list.append(1)
my_list.append(2)
my_list.append(3)
my_list.prepend(0)
my_list.display()  # 输出: 0 -> 1 -> 2 -> 3
my_list.delete(2)
my_list.display()  # 输出: 0 -> 1 -> 3
print(my_list.search(1))  # 输出: True

1.3 链表的应用场景

  1. 动态内存分配

    • 在操作系统的内存管理中,空闲内存块通常采用链表结构进行维护
    • 示例:Linux内核使用buddy allocator算法,将空闲内存块组织成多个不同大小的链表
    • 优势:可以灵活地分配和回收不同大小的内存块,减少内存碎片
  2. 实现其他数据结构

    • 链表可以作为基础结构实现多种高级数据结构:
      • 栈:通过单链表实现,只需维护头指针
      • 队列:通过双向链表实现,维护头尾指针
      • 哈希表:解决哈希冲突时常用链表法
    • 典型应用:Java的LinkedList类同时实现了List和Deque接口
  3. 文件系统

    • 文件目录结构常采用链表形式组织:
      • Unix文件系统中,目录项通过链表连接
      • FAT文件系统使用链表追踪文件簇
    • 优势:可以方便地进行文件的创建、删除和移动操作
  4. 浏览器历史记录

    • 浏览器历史记录通常采用双向链表实现:
      • 每个节点存储访问的URL和时间戳
      • 通过前驱和后继指针实现前进/后退功能
    • 实现细节:Chromium浏览器使用HistoryEntry链表管理导航记录
  5. LRU缓存淘汰算法

    • LRU(Least Recently Used)算法典型实现:
      • 双向链表维护访问顺序
      • 哈希表提供O(1)时间访问
    • 工作流程:
      1. 访问数据时移动到链表头部
      2. 缓存满时淘汰链表尾部数据
    • 应用实例:Redis、Memcached等缓存系统

二、栈(Stack)

2.1 基本概念

栈是一种后进先出(Last In First Out, LIFO)的线性数据结构,支持两种基本操作:

  • 入栈(Push):将元素添加到栈顶
  • 出栈(Pop):移除并返回栈顶元素

栈的特点是只能在一端进行操作,这一端称为栈顶,另一端称为栈底。栈的行为类似于现实生活中的一叠盘子,只能从顶部添加或移除盘子。

主要特性
  1. 后进先出原则:最后入栈的元素最先被移除
  2. 单端操作限制:所有操作都只能在栈顶进行
  3. 动态大小:栈的大小会随着元素的入栈和出栈而改变
常见操作

除了基本的push和pop操作外,栈通常还支持:

  • peek/top:查看栈顶元素但不移除
  • isEmpty:检查栈是否为空
  • size:获取栈中元素数量
实现方式

栈可以通过多种方式实现:

  1. 数组实现:使用固定大小的数组
  2. 动态数组实现:可以自动扩容的数组
  3. 链表实现:使用单向链表,将头结点作为栈顶
应用场景
  1. 函数调用栈:程序执行时保存函数调用信息
  2. 括号匹配:检查表达式中的括号是否匹配
  3. 撤销操作:软件中的撤销功能通常使用栈实现
  4. 表达式求值:中缀表达式转后缀表达式
  5. 浏览器历史记录:前进后退功能使用两个栈实现
示例代码(伪代码)
stack = []
stack.push(1)  # 栈:[1]
stack.push(2)  # 栈:[1, 2]
top = stack.pop()  # 返回2,栈:[1]

2.2 栈的实现

栈可以用数组或链表实现,以下是基于Python列表的实现:

class Stack:def __init__(self):self.items = []  # 使用列表存储栈元素def is_empty(self):"""判断栈是否为空"""return len(self.items) == 0def push(self, item):"""入栈操作"""self.items.append(item)def pop(self):"""出栈操作,返回栈顶元素"""if self.is_empty():raise Exception("栈为空,无法执行出栈操作")return self.items.pop()def peek(self):"""查看栈顶元素,但不移除"""if self.is_empty():raise Exception("栈为空")return self.items[-1]def size(self):"""返回栈的大小"""return len(self.items)# 使用示例
stack = Stack()
stack.push(1)
stack.push(2)
stack.push(3)
print(stack.pop())  # 输出: 3
print(stack.peek())  # 输出: 2
print(stack.size())  # 输出: 2

2.3 栈的应用场景

  1. 函数调用栈

    • 程序运行时,每次函数调用都会在调用栈中创建一个栈帧(stack frame),包含函数参数、局部变量和返回地址
    • 当函数返回时,对应的栈帧会被弹出
    • 示例:main()调用A(),A()调用B()时,调用顺序为main→A→B,返回顺序为B→A→main
  2. 表达式求值

    • 中缀表达式转后缀表达式(逆波兰表达式)时使用运算符栈处理优先级
    • 后缀表达式求值时使用操作数栈存储中间结果
    • 示例:中缀表达式"3+45"转为后缀表达式"345+",求值过程为:压入3→压入4→压入5→弹出4和5计算4*5=20→弹出3和20计算3+20=23
  3. 括号匹配检查

    • 遍历代码时遇到开括号就入栈
    • 遇到闭括号就出栈并检查是否匹配
    • 特别适用于编译器/解释器的语法检查
    • 可扩展检查多种括号类型:圆括号()、方括号[]、花括号{}
  4. 浏览器后退功能

    • 每访问新页面就将URL压入历史栈
    • 点击后退按钮时弹出栈顶URL
    • 前进功能通常需要辅助栈实现
    • 实际应用中可能采用更复杂的数据结构优化性能
  5. 递归算法实现

    • 每次递归调用相当于将当前状态压栈
    • 返回时从栈中恢复状态
    • 系统自动维护的调用栈可能限制递归深度
    • 深度过大时可能需人工用显式栈替代递归

示例:括号匹配检查

def is_matching(open_char, close_char):"""检查括号是否匹配的辅助函数"""pairs = { '(': ')', '[': ']', '{': '}' }  # 定义匹配规则return pairs.get(open_char) == close_char  # 查找对应闭括号def check_brackets(expression):"""检查表达式中的括号是否匹配参数:expression: 待检查的字符串表达式返回:bool: 所有括号是否匹配"""stack = Stack()  # 初始化空栈for char in expression:  # 遍历每个字符if char in '([{':  # 遇到开括号stack.push(char)  # 压栈elif char in ')]}':  # 遇到闭括号if stack.is_empty():  # 栈为空说明闭括号无匹配return Falsetop = stack.pop()  # 弹出栈顶开括号if not is_matching(top, char):  # 检查括号类型return Falsereturn stack.is_empty()  # 最终栈为空才表示完全匹配# 测试用例
test_cases = [("(a + [b * c])", True),    # 正确嵌套("(a + [b * c)", False),    # 缺少闭括号("a + b)", False),          # 多余闭括号("{[()]}", True),           # 多层嵌套("{[(])}", False),          # 交叉嵌套("", True)                  # 空字符串
]for expr, expected in test_cases:assert check_brackets(expr) == expected, f"测试失败: {expr}"
print("所有测试通过!")

扩展说明:

  1. 该算法时间复杂度为O(n),只需遍历字符串一次
  2. 可以扩展支持更多符号对,如HTML标签匹配
  3. 实际编译器使用时会结合其他语法分析技术
  4. 错误处理可以改进为返回具体错误位置

三、队列(Queue)

3.1 基本概念

队列是一种先进先出(First In First Out, FIFO)的线性数据结构,支持两种基本操作:

  • 入队(Enqueue):将元素添加到队列尾部
    • 示例:在银行排队时,新来的客户会排到队伍末尾
    • 实现方式:通常通过维护一个尾指针(rear)来记录最后一个元素的位置
  • 出队(Dequeue):移除并返回队列头部元素
    • 示例:银行柜台叫号时,总是服务排在队伍最前面的客户
    • 实现方式:通常通过维护一个头指针(front)来记录第一个元素的位置

队列的特点是元素从一端进入(称为rear/rear end),从另一端离开(称为front/front end),类似现实生活中的排队。这种特性使得队列特别适合需要按顺序处理的场景。

典型应用场景:
  1. 操作系统进程调度:CPU按进程到达顺序分配资源
  2. 打印机任务队列:打印任务按提交顺序依次执行
  3. 消息队列系统:如RabbitMQ等消息中间件
  4. 广度优先搜索:算法中需要按层次遍历节点
常见实现方式:
  • 数组实现(循环队列)
  • 链表实现
  • 标准库实现(如Java的Queue接口,C++的queue容器)

3.2 队列的实现

队列可以用数组或链表实现,以下是基于Python列表的实现:

class Queue:def __init__(self):self.items = []  # 使用列表存储队列元素def is_empty(self):"""判断队列是否为空"""return len(self.items) == 0def enqueue(self, item):"""入队操作,将元素添加到队列尾部"""self.items.append(item)def dequeue(self):"""出队操作,移除并返回队列头部元素"""if self.is_empty():raise Exception("队列为空,无法执行出队操作")return self.items.pop(0)def peek(self):"""查看队列头部元素,但不移除"""if self.is_empty():raise Exception("队列为空")return self.items[0]def size(self):"""返回队列的大小"""return len(self.items)# 使用示例
queue = Queue()
queue.enqueue(1)
queue.enqueue(2)
queue.enqueue(3)
print(queue.dequeue())  # 输出: 1
print(queue.peek())     # 输出: 2
print(queue.size())     # 输出: 2

3.3 队列的应用场景

  1. 任务调度:操作系统中的任务调度,按队列顺序执行任务
  2. 消息队列:分布式系统中,消息的生产和消费通常使用队列
  3. 广度优先搜索(BFS):图算法中,BFS使用队列来遍历节点
  4. 网络缓冲区:网络数据包的接收和处理按队列顺序进行
  5. 打印任务管理:打印机的任务队列管理多个打印请求

示例:广度优先搜索(BFS)

from collections import deque  # 使用Python内置的双端队列def bfs(graph, start):"""广度优先搜索算法"""visited = set()  # 记录已访问的节点queue = deque([start])  # 初始化队列visited.add(start)while queue:vertex = queue.popleft()  # 出队print(vertex, end=' ')    # 处理当前节点# 将所有未访问的邻居节点入队for neighbor in graph[vertex]:if neighbor not in visited:visited.add(neighbor)queue.append(neighbor)# 测试
graph = {'A': ['B', 'C'],'B': ['A', 'D', 'E'],'C': ['A', 'F'],'D': ['B'],'E': ['B', 'F'],'F': ['C', 'E']
}print("BFS traversal starting from 'A':")
bfs(graph, 'A')  # 输出: A B C D E F

四、链表、栈、队列的对比

数据结构访问方式插入/删除效率典型应用场景
链表顺序访问(需从头遍历)O(1)(指定位置,如双向链表首尾)1. 动态内存管理(操作系统内存分配)
2. LRU缓存淘汰算法
3. 文件系统目录结构
LIFO(后进先出)O(1)(仅限栈顶操作)1. 函数调用栈(保存返回地址和局部变量)
2. 括号匹配检测
3. 逆波兰表达式求值
队列FIFO(先进先出)O(1)(队尾插入,队首删除)1. 操作系统任务调度(打印队列)
2. 广度优先搜索(BFS)
3. 消息队列系统

补充说明:

  1. 链表插入效率示例:双向链表在已知位置插入节点只需修改相邻节点的4个指针
  2. 栈操作示例:函数调用时依次压栈(参数→返回地址→局部变量),返回时反向弹出
  3. 队列应用场景:BFS算法中队列保存待访问节点,保证按层次遍历图的节点

五、总结

链表、栈和队列作为基础数据结构,各自具有独特的特点和适用场景:

  1. 链表(Linked List)

    • 特点:节点通过指针相连,内存不连续
    • 优势:动态插入/删除效率高(时间复杂度O(1)),内存利用率好
    • 适用场景:音乐播放列表、浏览器历史记录、内存管理等
    • 实现方式:单链表、双链表、循环链表等
  2. 栈(Stack)

    • 特点:后进先出(LIFO)的数据结构
    • 核心操作:push(入栈)、pop(出栈),时间复杂度均为O(1)
    • 常见应用:
      • 函数调用栈(递归算法实现)
      • 括号匹配检查
      • 浏览器后退功能
      • 表达式求值(逆波兰表达式)
  3. 队列(Queue)

    • 特点:先进先出(FIFO)的数据结构
    • 核心操作:enqueue(入队)、dequeue(出队),时间复杂度均为O(1)
    • 常见应用:
      • 任务调度(如打印队列)
      • 消息队列系统
      • 广度优先搜索算法
      • 多线程资源共享
    • 变种:优先队列、循环队列、双端队列等

在实际开发中,应根据问题的特性选择合适的数据结构:

  1. 数据结构选择原则

    • 考虑数据访问模式(随机访问/顺序访问)
    • 评估操作频率(插入/删除/查询哪个更频繁)
    • 考虑内存使用效率
    • 评估算法复杂度需求
  2. 典型组合应用

    • 栈+队列:实现某些复杂算法(如二叉树的锯齿形遍历)
    • 链表+栈:实现撤销/重做功能
    • 链表+队列:实现LRU缓存淘汰算法
  3. 性能优化技巧

    • 对于频繁插入删除:优先考虑链表
    • 对于需要快速访问最新/最旧元素:考虑栈或队列
    • 对于需要随机访问:可能需要结合数组使用

通过合理应用这些数据结构,可以显著提高代码的执行效率(通常能降低1-2个数量级的时间复杂度)和可维护性(更清晰的逻辑表达)。建议在实际使用时,结合具体语言的标准库实现(如Java的LinkedList、Python的deque等)来获得最佳性能。

本文详细介绍了链表、栈和队列的实现方法及典型应用,通过代码示例和应用场景分析,帮助读者建立对这三种数据结构的全面理解。

📌 下期预告:树结构(二叉树、B树、红黑树)
❤️❤️❤️:如果你觉得这篇文章对你有帮助,欢迎点赞、关注本专栏!后续解锁更多功能,敬请期待!👍🏻 👍🏻 👍🏻
更多专栏汇总:
前端面试专栏
Node.js 实训专栏

数码产品严选
数码产品严选

http://www.dtcms.com/a/272255.html

相关文章:

  • NAT技术(网络地址转换)
  • 【实战】使用 ELK 搭建 Spring Boot Docker 容器日志监控系统
  • OSPF实验以及核心原理全解
  • 【SkyWalking】配置告警规则并通过 Webhook 推送钉钉通知
  • HP EVA SAN 数据恢复利器:Data recovery plugin for HP StorageWorks EVA
  • 前端项目集成husky + lint-staged + commitlint
  • Web-Bench:基于web标准和框架的benchmark
  • Fiddler中文版全面评测:功能亮点、使用场景与中文网资源整合指南
  • 什么是强化学习(RL)--2
  • 如何在VMware里的飞牛OS访问主机里面图片文件夹
  • 【运维实战】解决 K8s 节点无法拉取 pause:3.6 镜像导致 API Server 启动失败的问题
  • 【EGSR2025】材质+扩散模型+神经网络相关论文整理随笔(三)
  • 华为昇腾NPU与NVIDIA CUDA生态兼容层开发实录:手写算子自动转换工具链(AST级代码迁移方案)
  • 缓存穿透与击穿多方案对比与实践指南
  • 设计模式的六大设计原则
  • AI问答之手机相机专业拍照模式的主要几个参数解释
  • 【笔记】使用 html 创建网址快捷方式
  • 达梦数据库DMDRS搭建单向dm8-dm8数据同步
  • 【工具教程】批量提取OCR图片中固定文字保存WPS表格,批量OCR识别图像中的文字保存到Excel表格的操作步骤和注意事项
  • 虚拟环境已安装该包,且已激活,但报错
  • 智能体的记忆系统:短期记忆、长期记忆与知识图谱
  • Spring for Apache Pulsar->Reactive Support->Quick Tour
  • 【LeetCode100】--- 1.两数之和【复习回滚】
  • 氢能源杂谈
  • 深入拆解Spring核心思想之一:IoC
  • 天津医大用网络药理学+分子对接发表中科院二区IF5
  • 【Python】基于Python提取图片验证码
  • SYM32第二十天 ESP8266-01S和电脑实现串口通信(3)
  • 羊肚菌自动采收车设计cad【7张】+三维图+设计说明书
  • 电脑息屏工具,一键黑屏超方便