栈(stack)的那些事
1.1栈的简介
栈作为一种限制性访问数据结构,提供了push()压入和pop()弹出的方法,具有后进先出的特点,使得程序不容易出错。 关于栈的实现,输入的入栈和出栈的时间复杂度为O(1)。操作时间不依赖总体栈中的个数,因此操作时间非常快。
1.2 栈的Java实现
这里用底层数组用Object主要为了能存储不同对象,默认初始化数组大小为10,当push时候会先判断是否需要扩大数组容量。pop()时候会先取出数据,之后再删除。
public class ArrayStack {
//存储元素的数组,声明为Object类型能存储任意类型的数据
private Object[] elementData;
//指向栈顶的指针
private int top;
//栈的总容量
private int size;
//默认构造一个容量为10的栈
public ArrayStack(){
this.elementData = new Object[10];
this.top = -1;
this.size = 10;
}
public ArrayStack(int initialCapacity){
if(initialCapacity < 0){
throw new IllegalArgumentException("栈初始容量不能小于0: "+initialCapacity);
}
this.elementData = new Object[initialCapacity];
this.top = -1;
this.size = initialCapacity;
}
//压入元素
public Object push(Object item){
//是否需要扩容
isGrow(top+1);
elementData[++top] = item;
return item;
}
//弹出栈顶元素
public Object pop(){
Object obj = peek();
remove(top);
return obj;
}
//获取栈顶元素
public Object peek(){
if(top == -1){
throw new EmptyStackException();
}
return elementData[top];
}
//判断栈是否为空
public boolean isEmpty(){
return (top == -1);
}
//删除栈顶元素
public void remove(int top){
//栈顶元素置为null
elementData[top] = null;
this.top--;
}
/**
* 是否需要扩容,如果需要,则扩大一倍并返回true,不需要则返回false
* @param minCapacity
* @return
*/
public boolean isGrow(int minCapacity){
int oldCapacity = size;
//如果当前元素压入栈之后总容量大于前面定义的容量,则需要扩容
if(minCapacity >= oldCapacity){
//定义扩大之后栈的总容量
int newCapacity = 0;
//栈容量扩大两倍(左移一位)看是否超过int类型所表示的最大范围
if((oldCapacity<<1) - Integer.MAX_VALUE >0){
newCapacity = Integer.MAX_VALUE;
}else{
newCapacity = (oldCapacity<<1);//左移一位,相当于*2
}
this.size = newCapacity;
int[] newArray = new int[size];
elementData = Arrays.copyOf(elementData, size);
return true;
}else{
return false;
}
}
}
复制代码
1.3 应用--解析运算符
运算表4+3 * 2 如何运算?
机器从左到右的读取数据,第一次读到+运算符时候,继续读下一个数据,判断是否有优先级比+高,当读到*到最后时候,的优先级最高,这时候两边操作数先计算出结果。表达式变为4+6 ,又开始从左到右开始读。 从中我们能发现,当公式长的时候,这种方法就会变得非常慢了。 之所以会变得这么慢,主要还是我们写的公式机器读不懂,这样我们就把问题引导到了数据的存储问题上了,关于表达式我们定义了三种类型,以4+3 - 2为例子
前缀表达式:操作符在操作数前面 比如:+ - 432
中缀表达式:操作符在操作数中间,也就是4+3 -2
后缀表达式:操作符在操作数之后 ,比如: 4 3 +2-
相比较中缀表达式,前缀表达式和后缀表达式,两个比较容易读取,那么接下来只要将中缀表达式转换为后缀表达式或者是前缀表达式,问题就可以解决了。本文以后缀表达式为例子:
以中缀表达式4+3 * 2转换为后缀表达式为例:
当前扫描的元素 | a2(栈顶->低) | a1(栈顶->低) | 说明 |
---|---|---|---|
4 | 4 | 空 | 遇到操作数直接结果a2中 |
+ | 4 | + | a1为空的,直接进入 |
3 | 4,3 | + | 遇到操作数直接进入a2中 |
* | 4,3 | +,* | a1中栈顶+遇到优先级高的*自动加入 |
2 | 4,3,2 | +,* | 遇到操作数直接放到a2中 |
结束 | 4 3 2 * + | 空 | 最后将a1中栈顶依次弹入a2中 |
此时我们得到了后缀表达式43 2 * +,机器读取的后缀表达式规则:
后缀表达式计算机求值 |
---|
1、从左向有扫描 |
2、遇到数字,压入栈中 |
3、遇到运算符,弹出栈的两个数,并用运算符对这两个数相应计算,并将结构入栈 |
4、重复上述2、3步骤,知道表达式的最右端,最后的值即为表达式的结果 |
1.4 应用--深度优于搜索算法(DFS)
其实在图数据结构的深度优于搜索算法本质就是应用栈实现的, 深度优先搜索算法有如下规则:
规则1:如果可能,访问一个邻接的未访问顶点,标记它,并将它放入栈中。
规则2:当不能执行规则 1 时,如果栈不为空,就从栈中弹出一个顶点。
规则3:如果不能执行规则 1 和规则 2 时,就完成了整个搜索过程。
基于上述图,应用栈来说明:首先从1节点开始,查找2节点,此时第2节点比邻是3,5,将2弹入栈顶。选中第3节点,此时栈是[1,2,3] ,但是第3节点没有比邻了,那么将3弹出栈顶,回到了第2节点,因为第3节点已经被选过了,选中第5节点,此时栈是[1,2,5]...,依照规则,最后栈的结果[1,2,3,4,6,8,9]