“削减列。 依次读入新列 $c$,维护列栈 $S$。当栈非空时,取 $r=R[ S 1]$;若 $A {r,c}<A {r,S[ 1]}$,弹出栈顶并继续比较,否则停止。若 $ S < R $…”
形式陈述
用状态把接口说清楚
把栈的抽象状态写成有限序列
| 操作 | 返回值 | 操作后的状态 |
|---|---|---|
push(x) |
无 | |
top(),要求 |
原状态 | |
pop(),要求 |
||
isEmpty() |
原状态 |
这里采用 pop 返回被移除元素的约定;有些接口把“查看”和“删除”完全分开,使用时应以该接口为准。对空栈执行 pop、top 是下溢情形,需要规定为报错、返回失败标记,或违反前置条件,不能把未定义的值当作合法元素。固定容量的栈还需要规定满栈时的行为。
例如,从空栈依次执行 push(A)、push(B)、pop()、push(C),状态变化为
[] → [A] → [A, B] → [A] → [A, C]
返回 B 栈顶 C
随后两次弹出依次得到 C、A。LIFO 约束的是每次操作时仍在栈中的元素,并不是说所有输入必须先结束,才能开始输出。由状态定义还能直接得到一条检验规律:在任意合法栈上先 push(x) 再 pop(),会返回 x 并恢复原状态。
直觉
先处理最近留下的任务
读到一对嵌套括号的右半边时,应该先结束哪一层?答案是最近开启、尚未结束的那一层。栈把这种规则变成一个接口:元素只从同一端进入和离开,最后放入的元素最先取出,称为后进先出(LIFO)。这一端叫栈顶,另一端叫栈底。
栈是一种抽象数据类型:它规定允许怎样操作、操作返回什么,不规定元素怎样存放。动态数组和链表都能实现栈,但扩容、节点分配以及内存布局属于实现层。[1][2]
例子与边界
括号匹配为什么只需看栈顶
扫描括号串时,栈保存已经遇到、还没有匹配的左括号,按开启次序排列。遇到左括号就压入;遇到右括号,它只能关闭栈顶对应的那一层。因此必须同时检查“栈非空”和“括号类型相同”,检查通过才弹出。
对于 ([)],读到 ) 时栈底到栈顶依次为 (、[,栈顶是 [,所以立即判定不匹配。左右括号数量相等并不能保证结构正确;错误恰好发生在跨层关闭。对于 ([]),两个右括号依次关闭 [ 和 (,最后栈空,才说明所有层都已结束。
def brackets_match(text: str) -> bool:
"""检查三类括号;忽略其他字符。"""
stack: list[str] = []
partner = {')': '(', ']': '[', '}': '{'}
for ch in text:
if ch in '([{':
stack.append(ch)
elif ch in partner:
if not stack or stack.pop() != partner[ch]:
return False
return not stack
算法的不变量是:每次扫描后,栈中恰好是当前前缀尚未关闭的左括号。左括号保持这一事实,合法右括号删除最近的一项;遇到失配时,任何后续字符也不能补救已经发生的交叉。长度为
相同接口,成本保证可以不同
以下成本以顺序 RAM 模型、常数大小元素和常数成本的局部内存操作为前提;链式实现还把单个节点的分配、释放按常数成本计算。
| 表示方法 | push |
pop、top |
主要条件 |
|---|---|---|---|
| 固定容量数组 | 最坏 |
最坏 |
容量是契约的一部分 |
| 几何扩容的动态数组 | 摊还 |
top 最坏 pop 取决于缩容策略 |
扩容的单次 push 可为 |
| 表头作为栈顶的单链表 | 最坏 |
最坏 |
不包含非恒定的分配器开销 |
动态数组翻倍扩容时,连续压入
使用双端队列时,只使用同一端的一组操作,也能实现栈接口。但若把“删除任意中间元素”暴露给客户端,提供的就是比纯栈更强的接口;这与内部偶尔移动元素是两回事。
推论与应用
栈怎样组织算法的控制流
函数调用形成嵌套关系时,调用栈保存尚未完成的活动记录,其中可以包含返回位置、局部变量等信息。内层调用结束后,恢复最近暂停的外层调用,正好符合 LIFO。具体运行时可能优化尾调用,所以不应把每个源代码层面的调用都机械等同于新增一个物理栈帧。
深度优先搜索使用栈保存待继续探索的分支。递归写法让运行时维护这份状态;显式写法把它放进程序可管理的栈。若要精确复现递归访问次序,还需保存当前邻接表位置,或相应调整邻居的压栈顺序。
单调栈则在普通 LIFO 之上增加一个不变量:栈内元素按某种大小关系排列。新元素到来时,持续弹出已经失去候选资格的元素。线性时间通常来自每个元素至多入栈、出栈各一次,而不是因为一轮循环只能弹出一次。
与队列的先进先出规则相比,栈优先处理最近加入的元素。
参考资料
[1] Robert Sedgewick、Kevin Wayne,Algorithms, 4th ed., Addison-Wesley, 2011,§1.3: Bags, Queues, and Stacks:接口、链式实现、动态数组与缩容策略。
[2] MIT 6.031,2021 年春季课程讲义,Abstract Data Types:抽象行为与具体表示的分离。