栈
- 不是在说内存中的堆栈,这里的栈是指一种数据结构,可以基于数组或者链表实现;
- 基于数组:顺序栈;
- 基于链表:链式栈;
- 单从功能上来说,数组和链表都可以替代栈这种数据结构,但是数组和链表是最基础的数据结构,操作上自由灵活,使用不可控易出错,而栈在操作上做了限制,使得栈只能在特定的场景下使用;
- 当某个数据集合只涉及在一端插入删除数据,并且满足 先进后出,后进先出 的特性,首选 “栈”这种数据结构。
特点
- 先进后出,后进先出
顺序栈
- 基于数组构造,通过限制操作,只开放特定的操作接口,来实现栈,因为操作受限,有固定的操作特定,所以才叫做栈。
- 如下基于数组实现了一个顺序栈,开放的操作有:push入栈(将元素压入栈顶),pop出栈(弹出栈顶元素),peek查看栈顶;
public class ArrayStack {
private String[] items;
private int size;
private int capacity;
/**
* 构建一个数组栈
* @param capacity 指定栈容量
*/
public ArrayStack(int capacity) {
this.capacity = capacity;
this.size = 0;
this.items = new String[capacity];
}
/**
* 压入栈
* @param item
* @return
*/
public boolean push(String item){
if(size==capacity){
return false;
}
items[size++] = item;
return true;
}
/**
* 弹出栈顶元素
* @return
*/
public String pop(){
if(size==0){
return null;
}
return items[--size];
}
/**
* 查看栈顶元素
* @return
*/
public String peek(){
if(size==0){
return null;
}
return items[size-1];
}
}
- 基于数组实现的栈在容量上是由限制的,不过可以实现支持动态扩容,但每次扩容需要搬移数据;
- 插入复杂度最好O(1),最坏O(n),均摊O(1),删除(pop出栈)O(1);
- 基于链表实现的栈不存在扩容的问题;
/**
* 成长因子,数组容量不够时,扩容(java数组容器默认应该是1.5倍)
*/
private static final int GROW_FACTOR = 2;
/**
* 内部使用 ,扩充容量
*/
private void addCapacity(){
//容量翻倍
//搬移数据到新数组
//...
}
应用场景
函数调用栈
- 用来存储函数调用时需要的临时变量
- 每进入一个函数,就会将一个临时变量作为一个栈压入栈
- 当函数调用执行完成,将这个函数的对应栈帧全部出栈
- 至于为什么使用栈,那也是因为函数调用符合后进先出的特性
利用栈实现表达式求值
- 需要用到两个栈,一个存数值,一个存运算符
- 列出运算符优先级,例如( ) * / + - =,从高到低
- 从左到右遍历表达式,遇到数字压入数值栈,遇到运算符,与运算符栈顶比较优先级
- 栈顶运算符相较优先级高:则弹出栈顶运算符和先后连续弹出数值栈顶两个数值a,b,用弹出的运算符对这两个进行运算【后弹出b 运算符 先弹出a】,运算结果入数值栈,刚才遍历到的运算符继续和运算符栈顶比较优先级
- 栈顶运算符相较优先级低:运算符入栈
- 直至栈空
括号匹配检查
- (),{},[]假设两两嵌套 ({})、{()}、([])、[()]、{[()]}…都合法
- 遍历表达式,属于左括号入栈,属于右括号则与栈顶元素匹配
- 如果左右括号不匹配(不是左右匹配或者类型不匹配)则表达式格式非法
- 如果括号匹配,则弹出栈顶元素,继续遍历下一个。
两个栈,模拟浏览器前进后退功能;
两个栈X,Y
1、首次浏览的页面 ,压入X
2、前进-无效,后退,弹出X栈顶,压入Y
3、前进-弹出Y栈顶,压入X, 后退 -弹出X栈顶,压入Y
X栈空 不能后退
Y栈空 不能前进
熄灯
- 数组和链表是最基础的数据结构,从功能上来说,数组或链表确实可以替代栈,
- 但特定的数据结构是对特定场景的抽象,而且数组或链表暴露了太多的操作接口,操作上的确灵活自由,但使用时就比较不可控,自然也就更容易出错。
- 栈、队列、堆以及树都是在数组或者链表的基础上加入了特定的属性和操作限制的,适应不同场景的 应用型的数据结构。
- 操作受限制,但在特定的场景使用方便 ,可以实现一个栈类,特有属性,和特有的操作。