栈是限定仅在表尾进行插入或删除操作得线性表。从数据结构来看,栈也是一种线性结构。但由于其操作只能在表尾完成,因此是操作受限的线性表,也可称为限定性的数据结构。
栈的逻辑定义决定了栈具备了后进先出(last in first out,FILO)的明显特征,基于该特征,栈在计算机世界中有很多应用场景。
- 符号匹配
- 中缀表达式转换为后缀表达式
- 计算后缀表达式
- 实现函数的嵌套调用
- HTML 和 XML 文件中的标签匹配
- 网页浏览器中已访问页面的历史记录
- Java 中的程序计数栈、堆栈
- word的撤销操作
栈的操作逻辑决定了这种数据结构的ADT不会太复杂,其操作有构造方法、出栈、入栈、查看栈顶、获取容量、判空。下文将介绍使用java语言实现基于数组的栈和JDK1.8中提供的java.util.Stack类。
基于数组的Stack
由于栈是一种线性结构,很自然的考虑其存储数据的底层容器可以使用数组。使用数组实现栈逻辑时,数组的尾部是栈顶。栈的操作都在数组尾部完成,注意,此处的尾部指数组有值部分的最大索引位置,不是数组长度的尾部。
首先使用java中的接口,描述栈的所有操作。这可认为是将栈的ADT转换为接口形式。
public interface Stack<E> {
//获取栈先有大小,不是栈的容量
int getSize();
//出栈
E pop();
//入栈
void push(E e);
//获取栈顶元素
E peek();
//判空
boolean isEmpty();
}
使用动态数组实现Stack接口,其中动态数组的实现参考实现java动态数组
/**
* 该类的各个方法的时间复杂度都是O(1),
* 其中 push 和 pop有可能触发resize,但经过均摊后,仍是O(1)
* @param <E>
*/
public class ArrayStack<E> implements Stack<E> {
Array<E> arr;
public ArrayStack(int Capacity) {
arr = new Array<>(Capacity);
}
public ArrayStack(){
arr = new Array<>();
}
public int getCapacity(){
return arr.getCapacity();
}
@Override
public int getSize() {
return arr.getSize();
}
@Override
public E pop() {
return arr.remove(arr.getSize()-1);
}
@Override
public void push(E e) {
arr.addLast(e);
}
@Override
public E peek() {
return arr.getLast();
}
@Override
public boolean isEmpty() {
return arr.isEmpty();
}
@Override
public String toString(){
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("stack: [");
for (int i = 0; i < arr.getSize(); i++) {
stringBuilder.append(arr.get(i));
if(i == arr.getSize()-1){
stringBuilder.append("] top");
}
}
return stringBuilder.toString();
}
}
java.util.Stack<>
在java体系中,Stack 是 JDK 1.0 的产物,它继承自 Vector。基于 Vector 实现的栈 Stack。底层实际上还是数组,所以还是存在需要扩容。Vector 是由数组实现的集合类,它包含了大量集合处理的方法。而 Stack 之所以继承 Vector,是为了复用 Vector 中的方法,来实现进栈(push)、出栈(pop)等操作。这里就是 Stack 设计不好的地方,既然只是为了实现栈,不用链表来单独实现,而是为了复用简单的方法而迫使它继承 Vector,Stack 和 Vector 本来是毫无关系的。这使得 Stack 在基于数组实现上效率受影响,另外因为继承 Vector 类,Stack 可以复用 Vector 大量方法,这使得 Stack 在设计上不严谨。
在jdk1.8中,提供了
-
push() 入栈,返货入栈值
-
pop() :出栈,返回出栈值
-
peek() :获取栈顶元素
-
empty() :判断栈空
-
search(Object):以栈顶为1开始计数,返回元素位置
在日常代码实践中,由于性能问题,Vector已不被推荐使用,因此Vector的子类Stack也不再推荐使用,推荐使用Deque(Double Ended Queue,双端队列)。
若要使用java.util.Stack,只需记住该类提供的接口名称。同时,注意该类是一个泛型类,创建对象时注意传入类型,否则报错“Raw use of parameterized class ‘xxxx‘”
延申
- 对于一种数据结构,向用户提供的是逻辑功能,用户不会关心这种数据结构的底层实现。譬如栈,用户只需用栈后进先出的特点满足自己的业务需求,而不关心底层是怎么实现后进先出。用户关心的是数据结构的某一功能的输入输出。
- 各种数据结构,不管是队列,栈等线性数据结构还是树,图的等非线性数据结构,从根本上底层都是数组和链表。不管你用的是数组还是链表,用的都是计算机内存,物理内存是一个个大小相同的内存单元构成的