Keyboard shortcuts

Press or to navigate between chapters

Press ? to show this help

Press Esc to hide this help

复习

  • 数组与链表:两种基本的线性存储方式
  • 抽象数据类型:先规定能做什么,再决定怎么做
  • 函数调用与运行栈:调用信息如何被保存

TL;DR

  • 栈是“后进先出”的结构
  • 只在同一端插入和删除
  • 它天然契合函数调用、表达式求值和撤销
  • 用数组或链表都能实现

正文

  数组和链表描述的是“数据怎么摆”。而(stack)和下一章的队列,更强调“操作按什么顺序进行”。它们都是抽象数据类型,重点在接口,而不是底层用数组还是链表。

后进先出

  栈的规则只有一条,却极其有用:后进先出(LIFO,Last In First Out)。

  想象一摞盘子:你只能从最上面拿,也只能往最上面放。最后放上去的那个,最先被取走。

  它的核心操作通常有:

  • 压栈(push):把元素放到顶端
  • 出栈(pop):取走并移除顶端元素
  • 看栈顶(top):只看看顶端是谁,不取走

  所有操作都只在同一端进行,因此实现简单,代价也都是 O(1)。用数组或链表都能实现。

为什么它无处不在

  栈的价值在于:很多问题的处理顺序,天然就是“后进先出”。几个典型例子:

  • 函数调用:调用一个函数,它的局部变量、返回地址要先“压”起来;函数返回时再“弹”出去。前面讲编程基础时的“运行栈”,正是这个结构
  • 括号匹配:遇到左括号压栈,遇到右括号就看栈顶是不是对应的左括号
  • 表达式求值:把中缀表达式转成后缀,栈是关键工具
  • 撤销操作:每做一步就压栈,撤销时弹出最近一步

  拿“撤销”来说,它太贴合直觉了:你最后做的那件事,理应是第一个被撤销的。顺序天然吻合,栈就成了最自然的选择。

  这又回到那句老话:选数据结构,本质是选一种与问题天然契合的组织方式。 契合了,代码就简单,效率也高。

  那么,如果场景需要的是“先来先服务”呢?那就该轮到队列了。

思考题 1

  为什么函数调用天然适合用栈来组织?

思考题 2

  为什么“撤销”操作很适合用栈实现?

小结

知识点

  • 栈是后进先出的抽象数据类型
  • 只在一端进行压栈、出栈、看栈顶
  • 用数组或链表都能实现,基本操作 O(1)
  • 函数调用、括号匹配、表达式求值、撤销都用到栈

参考资料

  1. Wikipedia(zh):堆栈:后进先出的数据结构
  2. Wikipedia(zh):调用栈:函数调用信息的栈式存储

思考题答案(仅供参考)

思考题 1

  因为函数调用具有“后进先出”的嵌套关系:最近的调用必须最先返回。把局部变量、返回地址等按调用顺序压栈,返回时从栈顶依次弹出,正好符合这种嵌套结构。

思考题 2

  因为撤销需要“最后做的先撤”。每做一步就压入栈中,撤销时弹出最近的记录,顺序天然符合后进先出。用栈来实现,既直观又高效。

协议

  本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。

封面图

设计师 | 南国微雪