《学习 JavaScript 数据结构与算法》的地铁读书计划已经读到了图的深度优先遍历部分,现在边学边复习,用刚学的 TypeScript
重新捋一下每种数据结构的源代码。源代码参考自作者的Github 仓库。
栈
(Stack
)是一种遵从 后进先出
(Last In First Out
,LIFO
)原则的有序集合。新添加或待删除的元素都保存在栈的同一端,称作 栈顶
,另一端就叫 栈底
。在栈里,新元素都靠近栈顶,旧元素都接近栈底。
数据结构
一个栈应有几个操作方法如下:
push(element)
:入栈,添加新元素到栈顶。pop()
:出栈,移除栈顶的元素,同时返回被移除的元素。peek()
:返回栈顶的元素,不对栈做任何修改。isEmpty()
:栈是否为空。clear()
:移除栈里的所有元素。size()
:返回栈里的元素个数。toString()
: 覆盖 Object 默认的 toString 方法
数组实现
栈其实很类似一个功能不全的数组,只能从单端进出。所以用 Array 来实现栈数据结构是最简单的。
1 | export default class StackArray<T> { |
Map 实现
在使用数组时,大部分方法的时间复杂度是 O(n)
。找寻某个元素时,在最坏的情况下需要迭代数组的所有位置,所以如果直接使用字典 Map
来存储所有的栈元素能获得更好的性能。
1 | export default class Stack<T> { |
相关算法
栈的实际应用非常广泛。比如在 Javascript 运行时,就是用栈来存储变量和方法调用,每次调试和报错时,都可以看到这个调用栈。下面用栈来实现两个经典的算法。
进制转换
要把十进制转化成 N 进制,我们可以将该十进制数除以 N(N 进制是满 N 进一)并对商取整,直到结果是 0 为止。举个例子,把十进制的数 8039 转化成 N 进制的数字,过程算法如下所示:
所以可以用栈来存储余数,余数每个入栈,提取结果时余数挨个出栈,转换成算法如下:
1 | /** |
测试下:
1 | console.log(baseConverter(8039, 32)); // output => 7R7 |
汉诺塔
汉诺塔(Tower of Hanoi)源于印度传说中,大梵天创造世界时造了三根金刚石柱子,其中一根柱子自底向上叠着 64 片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。根据这个传说抽象的数学问题:
有三根杆子 A,B,C。A 杆上有 N 个 (N>1) 穿孔圆盘,盘的尺寸由下到上依次变小。要求按下列规则将所有圆盘移至 C 杆:
- 每次只能移动一个圆盘;
- 大盘不能叠在小盘上面;
如何移?最少要移动多少次?这里的算法在在知乎上看到了几个精彩的回答:如何理解汉诺塔的递归?。总的来说,就是利用数学归纳法,如下图所示,把算法分为四步:
首先看 b 图状态,该状态中最大的盘子在起始塔上,比它小的所有的盘子在辅助塔上。当前应该做的就是把最大的盘子从起始塔挪到目标塔,所以:
- 在执行该步骤之前,要做的就是把比它小的所有的盘子从起始塔挪到辅助塔;
- 在执行该步骤之后,要做的就是把比它小的所有的盘子从辅助塔挪到目标塔;
所以就形成了递归,每根塔都是只能从最上面挪入和挪出,所以可以用栈来完美的抽象表示,算法如下:
1 | import Stack from '../data-structures/stack'; |
测试下 5 个盘子的运算结果:
1 | [ |
下一篇来分析队列。
前端记事本,不定期更新,欢迎关注!