算法与数据结构

栈与队列:LIFO、FIFO 与循环队列

02-线性数据结构核心预计 15 分钟栈队列循环队列单调栈
学习状态:

一句话定义

栈(Stack)是后进先出(LIFO)的受限线性表,只暴露 push/pop/peek;队列(Queue)是先进先出(FIFO)的受限线性表,只暴露 enqueue/dequeue;两者都是「限制访问点」从而获得 O(1) 操作与确定性行为的 ADT。

为什么重要

它们是几乎所有遍历与回退行为的引擎:函数调用栈、DFS 用栈、BFS 用队列、回溯的状态恢复用栈、消息系统用队列。限制访问点反而让行为可预测、实现极简——这是「受限即强大」的第一个范例,也是本库后续 kp-011/kp-016/kp-027 的前置引擎。

前置知识

kp-001(ADT)、kp-006(数组实现基础)。

核心概念

  • 栈:操作集中在栈顶;典型应用:函数调用与递归、括号匹配、表达式求值、撤销操作、DFS、单调栈。
  • 队列:插入在尾、删除在头;典型应用:BFS、任务调度、消息队列、缓冲区。
  • 循环队列:数组实现队列的正确姿势——head/tail 指针对容量取模 (head+1) mod cap 循环复用空间,避免「假溢出」。
  • 判满/判空:牺牲一格((tail+1) mod cap == head 为满)或额外记 size,二选一并全库一致。
  • 双端队列(Deque):两头都能进出,是栈与队列的统一实现载体。
  • 单调栈:栈内元素保持单调性,O(n) 解决「下一个更大元素」类问题。

直观类比

栈是叠盘子:只能拿最上面的。队列是排队买票:先来先服务。循环队列则是「圆桌传菜」:桌子转一圈,空位循环再用,不必每次清桌。

原理与机制

数组栈:top 指向栈顶下一空位,push = a[top++],pop = a[--top],均摊 O(1)(扩容见 kp-004/kp-006)。循环队列:

入队: a[tail] ← x ; tail ← (tail+1) mod cap
出队: x ← a[head]; head ← (head+1) mod cap
判空: head == tail        判满: (tail+1) mod cap == head(牺牲一格)

括号匹配(栈的招牌应用):

对于 串中每个字符 c:
    若 c 是左括号: push(c)
    否则若 c 是右括号:
        若 栈空 或 pop() 不与 c 配对: 返回 不匹配
返回 栈空
时间 Θ(n),空间 O(n)。

图示

栈:        队列:  head ─▶[A][B][C][D]◀─ tail
 |C| ◀栈顶        入队在尾、出队在头
 |B|              循环队列(cap=6):
 |A|                  [F][G][ ][ ][D][E]
 └──                    ↑head        ↑tail(绕回)
出队两次后 head 越过末尾则取模回到 0,空间循环复用。

实例或案例

  • 函数调用栈:递归深度即栈深,RecursionError/栈溢出就是它爆了;把递归改显式栈迭代是通用的改造手段。
  • 编辑器撤销(Ctrl+Z)是栈;浏览器前进/后退是双栈。
  • 消息队列(RabbitMQ/Kafka 的队列语义)是队列 ADT 在分布式的放大,削峰填谷。
  • 单调栈求「每日温度:几天后更热」:每个元素至多进出栈一次,整体 O(n)。

常见误区

  • 数组实现队列不取模:head 左移后前部空间永远废弃,出现「假溢出」(明明有空位却报满)。
  • 忽视栈溢出:深递归在大输入上崩,应显式栈化或改迭代。
  • 两栈实现队列时说「dequeue 是 O(n)」而不加均摊:倒栈摊到每个元素上是均摊 O(1),口径要讲清(见 kp-004)。
  • 混淆双端队列与栈/队列:deque 能模拟两者,但业务上仍应按语义选用以约束行为。

自测题

  1. 设计一个支持 push/pop/获取最小值都 O(1) 的栈,说明思路与空间代价。

答案要点:辅助同步最小栈,push 时同步压入 min(新值, 当前最小);pop 两栈同弹;空间最坏 2n。

  1. 循环队列容量 6,head=4,tail=2(取模后),当前元素个数是多少?

答案要点:(tail − head + cap) mod cap = (2−4+6) mod 6 = 4。

  1. 用两个栈实现队列,enqueue/dequeue 的均摊复杂度各是多少?为什么是均摊?

答案要点:enqueue O(1);dequeue 均摊 O(1)(最坏 O(n)),因为每个元素至多经历一次入栈与一次倒栈。

与其他知识点的关系

kp-011 层序遍历用队列、递归遍历依赖调用栈;kp-016 BFS/DFS 是队列/栈的图上版本;kp-027 回溯的状态恢复与撤销依赖栈语义;kp-029 的匹配过程也与括号匹配同构。

延伸阅读

《算法导论》第 10.1 节「栈和队列」;《算法(第 4 版)》第 1.3 节 Dijkstra 双栈算术表达式求值。