定义
先入后出,有点类似将书放在抽屉里,先放进去的书,如果想拿到他,必须将他上面书拿完才可以,粗俗的形容可以这么比喻:“吃了吐”叫栈,“吃了拉”叫队列,话粗理不粗。栈是一种“操作受限”的线性表,只允许一段插入和删除数据。
从功能上看,数组或链表确实可代替栈,但是在特定的情况中,数组和链表暴露的接口太多,操作上虽然灵活,但是很多条件不可控,使用上当然容易出现问题。
当某个数据集合只涉及在一段插入和删除数据,并且满足先进后出的特性,我们就应该首选栈这种数据结构。
实现
根据其定义和特点,栈主要包含两个操作,入栈和出栈。数组和链表都可以实现,用数组实现的栈叫做 顺序栈,用链表实现的栈叫做 链式栈。后面会贴上具体的实现代码和测试用例。
栈存储数据只需要一个大小为 n 的数组就足够了,在操作数据的过程中,只需要一两个临时变量存储空间,所以空间复杂度是 O(1),至于入栈和出栈的时间复杂度也是 O(1)。
练习
数组实现栈(支持动态扩容)
需要注意点:
- 当数据不断增加,大小达到阈值,也就是容器满了,此时大小需要扩容到原来的两倍。
- 当数据不断减小,大小达到整个大小四分之一时,此时大小需要缩小为原来的 1/2。
1 | class ArrayStack<T> implements Iterable<T>{ |
这里如果需要支持加强 for 循环,则需要创建自定义实现 Iterator 类。
运行结果:
1 | push: 1 data: [1, null, null, null] |
链表实现栈
需要注意的是临界值的判断。
1 | class LinkedStack<T> implements Iterable<T> { |
运行结果:
1 | push: 1 data: 1 |
栈在括号匹配中的应用
括号一般都是一一对应的,那可以用栈保存未匹配的左括号,从左到又进行扫描。
1 | class StackTest1 { |
浏览器在栈的应用
一般浏览器支持回退和前进功能,其实可以通过栈来实现这个功能。
- 使用两个栈,一个存储后退的数据,一个存储前进的数据
- 每次打开新的网址,后退栈压入,前进栈清空
- 每次回退网站,后退栈出栈,前进栈压入
- 在已经后退基础上前进,后退栈压入,前进栈出栈
- 每次前进和后退都需要判断各自栈是否可以进行数据操作
1 | class StackTest2 { |
参考自极客时间