数据结构栈
栈是限制插入和删除只能在一个位置上进行的表。该位置是表的末端,叫做栈顶(top)。对栈的基本操作有进栈(push)和出栈(pop),前者相当于插入,后者相当于删除最后插入的元素。
栈有时又叫做LIFO(后进先出)表。最基本的操作是进栈和出栈,还有一些其他操作,例如清空栈、判断栈是否为空、返回栈中元素的位置等,都作为栈的操作指令系统的一部分。
栈的实现
由于栈是一个表,因此任何实现表的方法都能实现栈。所以,ArrayList和LinkedList都可以实现栈,而大部分实现栈的方式也都是通过这两种方式进行的。
下面,我们先来用ArrayList来实现,代码如下:
import java.util.*;
class ArrayStack<T>{
private ArrayList<T> list;
ArrayStack(){
list = new ArrayList<T>();
}
//入栈操作,并返回入栈元素
public T push(T item){
list.add(item);
return item;
}
//出栈操作,并返回出栈元素
public T pop(){
return list.remove(list.size()-1);
}
//查看栈顶部的元素,但不移除该元素
public T peek(){
return list.get(list.size()-1);
}
//测试栈是否为空
public boolean isEmpty(){
return list.isEmpty();
}
//返回元素在栈中的位置,以0为基数
public int search(Object o){
return list.indexOf(o);
}
}
在该实现类中,我们在构造器中创建了ArrayList的对象,并定义了栈的一些基本操作,其底层都是通过ArrayList来实现的。并加入泛型来使栈的使用者可以自定义栈中元素的类型。我们再用LinkedList来实现栈,先看代码:
import java.util.*;
class LinkedStack<T>{
private LinkedList<T> list;
LinkedStack(){
list = new LinkedList<T>();
}
//入栈操作,并返回入栈元素
public T push(T item){
list.addFirst(item);
return item;
}
//出栈操作,并返回出栈元素
public T pop(){
return list.removeFirst();
}
//查看栈顶部的元素,但不移除该元素
public T peek(){
return list.peekFirst();
}
//测试栈是否为空
public boolean isEmpty(){
return list.isEmpty();
}
//返回元素在栈中的位置,以0为基数
public int search(Object o){
return list.indexOf(o);
}
}
我们可以看到,LinkedList栈的实现方式与ArrayList的实现方式大致相同,只是底层调用的是LinkedList的特有方法。
细心的朋友可能会发现,在LinkedList的API中,提供了push()和pop()方法,说明在LinkedList类中,已经为我们提供了栈的实现,就不用我们自己去定义了,那他是怎么实现的呢?我们可以看一下LinkedList的源码。LinkedList中push()和pop()的源码如下:
public void push(E e) {
addFirst(e);
}
public E pop() {
return removeFirst();
}
可以看到,源码中入栈与出栈的实现方式底层也是调用的LinkedList的方法,跟我们自定义的方式是一样的,只不过他的返回值是void而已。
其实,LinkedList中的push()和pop()方法是实现自Deque()接口,该接口是Queue的子接口,JDK1.6出现。定义了队列和栈的实现方法。
Java1.0中的栈
早在Java1.0版本,就有栈的实现方式。即Stack类,该类实现了栈的基本操作方法,但是该类并不是通过类Vector来构建,而是直接继承了Vector,具有Vector的所有特点和行为,再加上一些Stack的特有行为。所以这算是一个比较糟糕的设计,我们只做了解,不用作使用。