栈这个词实际上在计算机科学里使用很多,除了数据结构外,还有内存里的栈区 (和堆对应),熟悉 C 系语言的话应该不会陌生。 上一章我们讲到了先进先出 queue,其实用 python 的内置类型 collections.deque 或者我们自己实现的 LinkedList 来实现它都很简单。 本章我们讲讲 后进先出的栈。

生活中的数据结构:

  • 栈。好比在桶里头放盘子,先放的盘子放在了底下,后来的盘子放在上边。你要拿的时候,也是先拿最上边的。

栈其实也很简单,因为基础操作就俩,一个 push 和一个 pop,咦,咋和队列一样的? 确实方法名字一样,但是得到的结果可是不同的。

栈 ADT

上一章我介绍了我们怎样选取恰到的数据结构来实现新的 ADT?你能想到这里我们应该使用之前提到的哪个数据结构来实现吗? 你的大脑可能开始高(gui)速(su)旋转了,上几章学过的 array, list, deque, LinkedList, CircularDoubleLinkedList, queue 等在大脑里呼啸而过,这个时候可能已经一脸愁容了,到底该选啥?

还用问嘛,当然是时间复杂度最小的啦,大部分情况下空间都是够用的。 其实你会发现栈比队列还简单,因为它只在顶上操作(想象装着盘子的桶),如果有一种数据结构能方便在尾部增减元素不就满足需求了吗。 这个时候如果你忘记了,可以翻翻前几章,看看哪个数据结构符合要求。

想一下,似乎 CircularDoubleLinkedList 循环双端队列是满足的,因为增删最后一个元素都是 O(1)。 不过看了下示例代码,似乎没有 pop() 方法,对,因为我已经把实现 deque 作为思考题了。😂 如果之前你没写出来也没关系,这里我们会再实现它。

视频里我们将借助 CircularDoubleLinkedList 实现 双端队列 Deque ,并且用 Deque 实现 Stack。

Stack over flow 什么鬼?

嗯,stackoverflow 不是一个程序员问答网站吗?没错。 函数的临时变量是存储在栈区的,如果你不幸写了一个没有出口的递归函数,就会这个错。不信你试试:

def infinite_fib(n):
    return infinite_fib(n-1) + infinite_fib(n-2)
infinite_fib(10)

一大段输出之后就会出现异常: RecursionError: maximum recursion depth exceeded。 后边会讲到递归,递归是初学者比较难理解的概念,在树的遍历等地方还会看到它。

数据结构头脑风暴法

当我们不知道使用什么数据结构来解决问题的时候,《程序员面试金典》这本书的第六章提到了一种方式叫做『数据结构头脑风暴法』。 这种笨方法就是快速过一遍数据结构的列表,然后逐一尝试各种数据结构看看哪个最适合。

在你实现一个更高级的数据结构的时候,如果脑子没有思路,不妨尝试下这个方法,迅速过一遍你所知道的数据结构,看看哪种最适合。(从每个操作的时间复杂度和空间复杂度分析寻找最优解)

思考题

  • 上一章我们用数组实现了队列,其实也能用数组来实现栈,你能自己用数组来实现一个栈的 ADT 吗?
  • 实际上借助 python 内置的 list/collections.deque 结构就很容易实现一个栈,请你尝试实现,本章我们全部使用自己编写的数据结构而没用到 python 内置的数据结构。
  • 这里我们自己实现了 Deque,你能用 python 内置的 collections.deque 实现栈吗?有轮子能直接用的话看起来就简单多了,这里我们为了学习数据结构的实现就避免了直接使用内置结构
  • 哪些经典算法里使用到了栈呢?

Leetcode 练习

https://leetcode.com/problems/implement-queue-using-stacks/