栈的特点是先进先出,只允许在一端插入和删除数据。
栈顶:栈的最后一个元素
栈底:栈的第一个元素
出栈:从栈顶删除最后一个元素
入栈:向栈顶压入新的元素
如何实现一个“栈”?
栈可以用数组实现,也可以用链表来实现。用数组实现的栈,我们叫作顺序栈,用链表实现的栈,我们叫作链式栈。
下面是一个基于一个基于数组的顺序栈
// 基于数组实现的顺序栈public class ArrayStack {private String[] items; // 数组private int count; // 栈中元素个数private int n; // 栈的大小// 初始化数组,申请一个大小为 n 的数组空间public ArrayStack(int n) {this.items = new String[n];this.n = n;this.count = 0;}// 入栈操作public boolean push(String item) {// 数组空间不够了,直接返回 false,入栈失败。if (count == n) return false;// 将 item 放到下标为 count 的位置,并且 count 加一items[count] = item;++count;return true;}// 出栈操作public String pop() {// 栈为空,则直接返回 nullif (count == 0) return null;// 返回下标为 count-1 的数组元素,并且栈中元素个数 count 减一String tmp = items[count-1];--count;return tmp;}}
时间、空间复杂度为O(1)
function Stack(){var items = [];//入栈this.push = function(elem){items.push(elem);}//出栈this.pop = function(){return items.pop();}//返回栈顶元素this.peek = function(){return items[items.length -1];}//是否是空栈this.isEmpty = function(){return items.length;}//栈的长度this.size = function(){return items.length;}//清空栈this.clear = function(){items = [];}//打印栈this.print = function(){console.log(items.toString());}}
支持动态扩容的顺序栈
实现一个支持动态扩容的栈,只需要底层依赖一个支持动态扩容的数组就可以了。
栈的应用
栈在函数调用中的应用
从代码中我们可以看出,main() 函数调用了 add() 函数,获取计算结果,并且与临时变量 a 相加,最后打印 res 的值。为了让你清晰地看到这个过程对应的函数栈里出栈、入栈的操作,我画了一张图。图中显示的是,在执行到 add() 函数时,函数调用栈的情况。
栈在表达式求值中的应用
表达式求值是通过两个栈来实现,一个栈保存操作数,另一个栈保存运算符。
从左向右遍历表达式,当遇到数字时,将数字压入操作数栈;当遇到操作符时,与运算符栈的栈顶元素比较。
如果比运算符栈顶元素的优先级高,就将当前运算符压入栈;
如果比运算符栈顶元素的优先级低或者相同,从运算符中取栈顶运算符,从操作数栈的栈顶取2个操作数,然后进行计算,再把计算结果压入操作数栈,继续比较。
栈在括号匹配中的应用
用来检测括号的嵌套是否合法
解答开篇
实现浏览器的前进和后退功能,使用两个栈(X和Y)实现。
- 首次浏览的页面依次压入栈X
- 当点击后退时,再依次从栈X中出栈,并将出栈的数据依次压入栈Y
- 当点击前进时,依次从栈Y中出栈,并将出栈的数据依次压入X
- 当栈X中没有数据时,说明没有页面可以继续后退浏览
- 当栈Y中没有数据时,说明没有页面可以继续前进浏览
课后思考
1.为什么函数调用要用“栈”来保存临时变量?用其他数据结构不行吗?
参考答案:
