栈与队列数据结构详解

栈(Stack)和队列(Queue)这两种基础且非常重要的线性数据结构 。它们的核心区别在于数据元素的添加(插入)和移除(删除)遵循的规则不同

1. 栈 (Stack)

  • 核心理念: 后进先出 (Last-In-First-Out, LIFO) 。想象一摞盘子:你总是把新盘子放在最上面(添加),也总是从最上面拿走盘子(移除)。最后放上去的盘子,总是最先被拿走。
  • 操作限制: 所有的插入(push)和删除(pop)操作都只能在结构的一端进行,这一端被称为 栈顶 (Top) 。另一端称为 栈底 (Bottom) 。不能从中间或底部直接操作元素。
  • 核心操作:
    • push(item) / 入栈 将一个新元素添加到栈顶。栈的大小增加。
    • pop() / 出栈 移除并返回栈顶的元素。栈的大小减小。尝试从一个空栈 pop 通常会导致错误(栈下溢)。
    • peek() / top() / 查看栈顶 返回栈顶的元素,但不移除它。只是看一眼栈顶是什么。
    • isEmpty() / 判空 检查栈是否为空。
    • isFull() (仅在固定大小栈中需要): 检查栈是否已满(如果栈的大小是固定的)。
  • 类比:
    • 一摞盘子
    • 弹夹(子弹压入弹夹,射击时弹出最后压入的子弹)
    • 摞起来的书
    • 网页浏览器的"后退"按钮历史记录(最后访问的页面最先返回)
    • 文档编辑器的"撤销"(Undo) 功能(最后执行的操作最先撤销)
  • 实现方式:
    • 数组 (Array): 使用一个数组和一个指向栈顶的索引(通常初始化为 -1 表示空栈)。push 时索引递增并赋值,pop 时返回索引处值并递减索引。实现简单,访问快,但大小通常固定(或动态扩容有开销)。
    • 链表 (Linked List): 使用单链表,通常将链表的头节点 作为栈顶push 相当于在链表头部插入新节点,pop 相当于删除链表头节点并返回其值。大小可以灵活增长,但每个节点有额外指针开销。
  • 时间复杂度: 所有核心操作 (push, pop, peek, isEmpty) 在数组和链表实现中通常都是 O(1) 常数时间复杂度。
  • 关键特性:
    • 元素的添加和移除严格限制在栈顶一端。
    • 访问具有局部性:你只能直接访问栈顶元素。要访问栈底元素,必须先移除其上所有元素。
    • 顺序由添加顺序严格反向决定:最后添加的最先移除。
  • 典型应用:
    • 函数调用栈: 程序执行时记录函数调用链、局部变量、返回地址。调用函数时压栈,返回时出栈。
    • 表达式求值: 将中缀表达式转换为后缀/前缀表达式,以及计算后缀表达式(使用操作数栈和运算符栈)。
    • 括号匹配: 检查代码或表达式中括号 (), [], {} 是否成对且嵌套正确。
    • 撤销/重做机制 (Undo/Redo): 将用户操作按顺序压入栈,撤销时弹出并执行逆操作(可能需要两个栈)。
    • 深度优先搜索 (DFS): 在图或树的遍历中,使用栈来记录待访问的节点。
    • 回溯算法: 记录决策路径,当需要回退时从栈顶弹出状态。

2. 队列 (Queue)

  • 核心理念: 先进先出 (First-In-First-Out, FIFO) 。想象人们在超市收银台排队:新来的人排在队伍末尾(添加),排在队伍最前面的人最先被服务并离开队伍(移除)。最早排队的人,最早得到服务。
  • 操作限制: 元素的添加(enqueue)发生在结构的一端,称为 队尾 (Rear / Tail) 。元素的移除(dequeue)发生在结构的另一端,称为 队头 (Front / Head) 。不能从中间直接操作元素。
  • 核心操作:
    • enqueue(item) / 入队 / offer(item) 将一个新元素添加到队尾。队列的长度增加。
    • dequeue() / 出队 / poll() 移除并返回队头的元素。队列的长度减小。尝试从一个空队列 dequeue 通常会导致错误(队列下溢)。
    • peek() / front() / 查看队头 返回队头的元素,但不移除它
    • isEmpty() / 判空 检查队列是否为空。
    • isFull() (仅在固定大小队列中需要): 检查队列是否已满。
  • 类比:
    • 超市收银台排队
    • 打印机任务队列(先提交的文档先打印)
    • 客服电话等待队列(先打进来的电话先被接通)
    • 水管(水从一端流入,从另一端流出,先流入的水先流出)
  • 实现方式:
    • 简单数组: dequeue 后所有元素需要前移(时间复杂度 O(n),效率低)。不常用。
    • 循环数组 (Circular Buffer / Ring Buffer): 将数组视为首尾相连的环。frontrear 在到达数组末尾时会绕回到开头。高效利用空间,是固定大小队列的常用实现。需要处理队列满/空的条件(通常通过一个计数器或区分 rear+1 == front 表示满和 rear == front 表示空)。
    • 数组 (Array): 使用一个数组和两个索引:front 指向队头元素,rear 指向下一个要插入的位置(队尾)。
    • 链表 (Linked List): 使用单链表或双链表。维护两个指针:head 指向链表的第一个节点(队头),tail 指向链表的最后一个节点(队尾)。enqueuetail 后添加新节点并更新 taildequeue 移除 head 节点并更新 head。大小灵活增长,是动态队列的首选实现。
  • 时间复杂度:
    • 链表实现: enqueue, dequeue, peek, isEmpty 都是 O(1)
    • 循环数组实现: enqueue, dequeue, peek, isEmpty 也都是 O(1)
  • 关键特性:
    • 元素的添加和移除严格限制在两端:添加在队尾,移除在队头。
    • 元素的处理顺序严格遵循其到达的先后顺序:最早添加的最先移除。
  • 典型应用:
    • 任务调度: 操作系统进程调度(如先来先服务 FCFS)、线程池任务队列。
    • 消息队列: 分布式系统中,生产者将消息放入队列,消费者从队列中取出消息处理,实现解耦和异步通信(如 RabbitMQ, Kafka)。
    • 广度优先搜索 (BFS): 在图或树的遍历中,使用队列来记录待访问的节点(按层级遍历)。
    • 数据缓冲: 在数据传输速率不匹配的两个组件之间充当缓冲区(如 IO 缓冲区、网络数据包缓冲区)。生产者写入缓冲队列,消费者从缓冲队列读取。
    • 键盘缓冲区: 记录用户按下的按键顺序,供系统按序处理。
    • 打印任务管理: 多台计算机共享一台打印机时,打印任务按提交顺序排队等待。

栈 vs 队列 对比总结

特性 栈 (Stack) 队列 (Queue)
核心理念 LIFO (后进先出) FIFO (先进先出)
操作端 仅一端 (栈顶) push / pop 两端 enqueue(队尾) / dequeue(队头)
类比 一摞盘子、弹夹 排队、水管
添加操作 push() enqueue() / offer()
移除操作 pop() dequeue() / poll()
查看操作 peek() / top() peek() / front()
典型应用 函数调用栈、表达式求值、括号匹配、撤销操作、DFS 任务调度、消息队列、BFS、缓冲、打印队列
基本实现 数组、链表 循环数组、链表
核心操作时间复杂度 O(1) (push, pop, peek) O(1) (enqueue, dequeue, peek) (链表和循环数组)

关键区别要点

  1. 操作顺序规则: 栈是 LIFO(最后进来最先出去),队列是 FIFO(最先进来最先出去)。这是最本质的区别。
  2. 操作位置: 栈的所有操作(增删查)都集中在栈顶 这一个点。队列的操作分散在队尾(添加)队头(移除) 两个点。
  3. 行为类比: 栈是垂直的"叠加",队列是水平的"排队"。

理解栈和队列的核心在于牢牢把握 LIFO 和 FIFO 这两个基本原则,以及它们对操作位置和顺序的限制。它们在算法和系统设计中无处不在,是构建更复杂结构和逻辑的基础模块。

相关推荐
皮皮林5511 小时前
SpringBoot 加载外部 Jar,实现功能按需扩展!
java·spring boot
rocksun2 小时前
认识Embabel:一个使用Java构建AI Agent的框架
java·人工智能
Java中文社群3 小时前
AI实战:一键生成数字人视频!
java·人工智能·后端
王中阳Go3 小时前
从超市收银到航空调度:贪心算法如何破解生活中的最优决策谜题?
java·后端·算法
shepherd1113 小时前
谈谈TransmittableThreadLocal实现原理和在日志收集记录系统上下文实战应用
java·后端·开源
维基框架3 小时前
Spring Boot 项目整合Spring Security 进行身份验证
java·架构
日月星辰Ace4 小时前
Java JVM 垃圾回收器(四):现代垃圾回收器 之 Shenandoah GC
java·jvm
天天摸鱼的java工程师5 小时前
商品详情页 QPS 达 10 万,如何设计缓存架构降低数据库压力?
java·后端·面试
天天摸鱼的java工程师5 小时前
设计一个分布式 ID 生成器,要求全局唯一、趋势递增、支持每秒 10 万次生成,如何实现?
java·后端·面试
阿杆5 小时前
一个看似普通的定时任务,如何优雅地毁掉整台服务器
java·后端·代码规范