什么是栈
- 后进先出,先进后出,这就是典型的栈结构。
- 从栈的操作特性来看,是一种操作受限的线性表,只允许在端插入和删除数据。
为什么需要栈
- 任何数据结构都是对特定应用场景的抽象,数组和链表虽然使用起来更加灵活,但是暴露了几乎所有的操作,难免会引发错误。
- 当某个数据集合只涉及在某端插入和删除数据,且满足后进者先出、先进者后出的操作特性时,我们应该首选栈这种数据结构。
如何实现栈
- 栈的API
public class Stack<item>{
// 压栈
public void push(Item item){}
// 弹栈
public Item pop(){}
// 是否为空
public boolean isEmpty(){}
// 栈中数据量
public int size(){}
// 查看最近添加的数据,却不删除它
public Item peek(){}
}
- 数组实现(自动扩容)
- 时间复杂度:根据均摊复杂度的定义,可扩容数组实现的栈为O(1);
- 空间复杂度:出入栈时只需要一两个临时变量存储空间O(1),是指存储数据之外,算法运算需要的额外存储空间;
- 链表实现(时间和空间复杂度都是O(1))
栈的应用
- 栈在函数调用中的应用:操作系统给每个线程分配了一块独立的内存空间,这块内存空间被组织成栈这种结构,用来存储函数调用时的临时变量。每进入一个函数,就会将其中的临时变量作为栈帧入栈,当被调用的函数执行完成,返回之后,讲这个函数对应的栈帧出栈。
- 栈在表达式求值(比如:34+13*9+44-12/13)中的应用:利用两个栈,其中一个用来保存操作,另一个用来保存运算符。我们从左向右遍历表达式,当遇到数字我们就压入操作数栈;当遇到运算符,就与运算符栈的栈顶元素进行比较,若比运算符栈顶元素的优先级高,就当将当前运算符压入栈;若比运算符栈顶元素优先级低或者相同,从运算符中取出栈顶运算符,从操作数栈顶取出2个操作数,然后进行计算,把计算完的结果压入操作数栈,继续比较。
- 栈在括号匹配(比如{[]{}})中的应用:用栈保存为匹配的左括号,从左到右依次扫描字符串,当扫描到左括号时,则将其压入栈中;当扫描到右括号时,从栈顶取出一个左括号,如果能匹配上,则继续扫描剩下的字符串。如果扫描过程中,遇到不能配对的右括号,或者栈中没有数据,则说明为非法格式。当所有的括号都扫描完成之后,如果栈为空,就说明字符串合法;反之字符串非法。
- 如何实现浏览器的前进和后退功能:我们使用两个栈X和Y,我们把首次浏览的页面依次压入X,当点击后退按钮时,再次依次从栈X中出栈,并将出栈的页面依次放入Y栈。当栈X中没有数据时,说明没有页面可以浏览后退浏览了。当Y栈没有数据,那就说明没有页面可以点击前进浏览了。
《数据结构与算法之美》 -- 王争