栈
栈遵循后进先出(LIFO):push 把元素放到栈顶,pop 取出最后放入的元素。只操作同一端,不移动中间元素,也不用扫描。用本课 Python 列表实现时,这两项操作的摊还时间为 O(1)。
把栈看成叠放的盘子:从顶端放入,也从顶端取走。这个限制适合需要逆序恢复的过程,如函数调用、撤销记录、嵌套括号匹配和深度优先遍历。
栈如何工作
代码用 Python 列表 data 和容量 max 实现有界栈。push(val) 先检查 len(self.data) >= self.max。满栈抛出 Stack Overflow,否则在列表末尾追加 val。pop() 先检查是否为空;空栈抛出 Stack Underflow,否则删除并返回末尾元素。
栈顶就是列表最后一个索引。append 和 list.pop() 都操作这一端,不需要处理下面的元素。
容量上限来自本课实现,不是 LIFO 的要求。嵌入式系统常使用有界栈,普通 Python 列表则可随可用内存扩展。容量滑块的 3–10 只是画布限制。
逐步演示
默认栈为空,容量为 8。用压栈、出栈按钮运行:
push(3):0 < 8,检查通过,栈为[3],栈顶索引为 0。push(7):栈变为[3, 7],新元素位于旧栈顶上方。push(1):栈变为[3, 7, 1]。pop():返回最后加入的1,留下[3, 7]。pop():返回7,留下[3]。push(9):得到[3, 9],复用原来放7的位置。- 连续两次
pop():依次返回9、3,栈恢复为空。再出栈一次就下溢。
再试满栈:容量为 8 时,第九次压栈在追加前抛出上溢,失败操作不改变状态。
画布将栈画成顶部开口的容器。元素从顶部进入或离开,标题区显示 size 与栈顶值;空栈时隐藏栈顶值,避免残留旧状态。上面的序列给出了完整状态记录。
时间与空间复杂度
| 操作 | 时间 | 空间 |
|---|---|---|
push | 摊还 O(1) | 每个元素 O(1) |
pop | 摊还 O(1) | — |
peek | O(1) | — |
| 全部存储 | — | O(n) |
接口只允许访问栈顶。结合列表末端操作,才得到这里的成本;数据结构的规则与具体存储实现要一起考虑。
边界情况
- 满栈压入:抛出上溢,状态不变。
- 空栈弹出:抛出下溢;先检查是否为空,或捕获异常。
- 单个元素:压入后弹出同一个值,大小为 0 → 1 → 0。
- 重复值:允许。栈保存顺序,不要求值唯一。
常见错误
- 把
pop当成peek。pop会删除元素。只想查看时读取data[-1],并先处理空栈。 - 两端混用。 从尾部压入、头部取出就变成了队列。
- 省略空检查。 Python 的空列表
pop()抛出异常,JavaScript 数组则返回undefined;都应按 API 处理。 - 允许任意中间索引。 调用者直接读取
data[k]后,接口不再限制为 LIFO。需要这种访问时考虑列表或双端队列。 - 忽略调用栈。 函数调用压入栈帧,返回时弹出。递归中的栈溢出,是运行时调用栈耗尽。