栈
当某个数据集合致设计在一端插入和删除数据,并且满足后进先出、先进后出的特性,我们就应该首选“栈”这种数据结构。
栈主要包括两个操作,入栈和出栈,在栈顶插入一个数据和从栈顶删除一个数据。
栈既可以用数组来实现,也可以用链表来实现。用数组实现的栈,就做顺序栈,用链表实现的栈,叫链式栈。
不管是顺序栈还是链式栈,我们存储数据只需要一个大小为n的数组就够了。在入栈和出栈过程中,只需要一两个临时变量存储空间,所以空间复杂度是O(1)。
注意,这里存储数据需要一个大小为n的数组,并不是说空间复杂度就是O(n)。因为这个空间是必须的,无法省掉。所以我们说空间复杂度的时候,是指除了原本的数据存储空间外,算法运行还需要额外的存储空间。
栈的一些应用:
- 栈在函数调用中的应用
- 栈在表达式求值中的应用
- 栈在括号匹配中的应用
关于用函数调用栈来保存临时变量,为什么函数调用要用“栈”来保存变量呢?用其他数据结构不行吗?
解答: 其实,不一定非要栈来保存临时变量,只不过如果这个函数调用符合后进先出的特性,用栈这种数据结构来实现,是最顺理成章的选择。
从调用函数进入被调用函数,对于数据来说,变化的是什么?是作用域。所以根本上,只要能保证每进入一个新的函数,都是一个新的作用域就可以。而要实现这个,用栈就非常方便。在进入被调用函数的时候,分配一段栈空间给这个函数的变量,在函数结束的时候,将栈顶复位,正好回到调用函数的作用域内。
数组实现顺序栈
支持动态扩容的顺序栈,底层依赖一个支持动态扩容的数组就可以了。当栈满了,就申请一个更大的数组,将原来的数据搬移到新数组中。
实际上,支持动态扩容的顺序栈,平时开发并不常用到。
出栈的时间复杂度是O(1),那入栈操作的 均摊时间复杂度是O(1)。 关于分析,参考极客时间08|栈
数组实现栈的代码
1 | package Stack; |
链表实现链式栈
思路
- 用链表结点存储栈顶top节点,存储链表长度
- 实现isEmpty()方法、size()方法
- push()用增加的节点作为头节点
链表实现栈的代码
1 | package Stack; |
顺序和链式栈测试代码
1 | package Stack; |