如何理解“栈”?
- 后进者先出,先进者后出,这就是典型的“栈”结构。
- 从栈的操作特性上来看,栈是一种“操作受限”的线性表,只允许在一端插入和删除数据。
为什么还要用这个“操作受限”的“栈”呢?
- 特定的数据结构是对特定场景的抽象,而且,数组或链表暴露了太多的操作接口,操作上的确灵活自由,但使用时就比较不可控,自然也就更容易出错。
如何实现一个“栈”?
栈既可以用数组来实现,也可以用链表来实现。用数组实现的栈,我们叫作顺序栈,用链表实现的栈,我们叫作链式栈。
顺序栈和链式栈,时间、空间复杂度都是 O(1)。
支持动态扩容的顺序栈
- 底层依赖一个支持动态扩容的数组
- 当栈满了之后,申请一个更大的数组,将原来的数据搬移到新数组中。
出栈
的时间复杂度仍然是 O(1)。入栈
操作:当栈中有空闲空间,入栈操作的时间复杂度为 O(1);空间不够时,需要重新申请内存和数据搬移,时间复杂度为O(n)。- 平均情况下的耗时接近O(1)
栈在函数调用中的应用
函数调用栈
操作系统给每个线程分配了一块独立的内存空间,这块内存被组织成“栈”这种结构, 用来存储函数调用时的临时变量。每进入一个函数,就会将临时变量作为一个栈帧入栈,当被调用函数执行完成,返回之后,将这个函数对应的栈帧出栈。
栈在表达式求值中的应用
- 编译器就是通过两个栈来实现的。其中一个保存操作数的栈,另一个是保存运算符的栈。我们从左向右遍历表达式,当遇到数字,我们就直接压入操作数栈;当遇到运算符,就与运算符栈的栈顶元素进行比较。
- 如果比运算符栈顶元素的优先级高,就将当前运算符压入栈;如果比运算符栈顶元素的优先级低或者相同,从运算符栈中取栈顶运算符,从操作数栈的栈顶取2个操作数,然后进行计算,再把计算完的结果压入操作数栈,继续比较。
栈在括号匹配中的应用
借助栈来检查表达式中的括号是否匹配:
用栈来保存未匹配的左括号,从左到右依次扫描字符串。当扫描到左括号时,则将其压入栈中;当扫描到右括号时,从栈顶取出一个左括号。如果能够匹配,比如“(”跟“)”匹配,“[”跟“]”匹配,“{”跟“}”匹配,则继续扫描剩下的字符串。如果扫描的过程中,遇到不能配对的右括号,或者栈中没有数据,则说明为非法格式。
解答开篇
如何实现浏览器的前进、后退功能?
我们使用两个栈,X和Y,我们把首次浏览的页面依次压入栈X,当点击后退按钮时,再依次从栈X中出栈,并将出栈的数据依次放入栈Y。当我们点击前进按钮时,我们依次从栈Y中取出数据,放入栈X中。当栈X中没有数据时,那就说明没有页面可以继续后退浏览了。当栈Y中没有数据,那就说明没有页面可以点击前进按钮浏览了。