2、栈
栈(stack)又称堆栈,是一运算受限的线性表。其限制是只允许在线性表的一端进行插入与删除操作,不允许在 其他任何地方进行插入、删除、读取等操作。表中进行读取插入删除的一端称为栈顶(top),栈顶保存的元素称为栈顶元素,另外一端称为栈底(bottom)。
当栈中没有元素时称为空栈,向一个栈中插入元素又称为入栈或进栈,从栈中删除一个元素称为出栈或退栈。由于栈的操作都在栈顶进行,因此后进栈的元素必定先出栈,所以又把栈称为后进先出表(Last In First Out),简称LIFO。
和顺序表类似,堆栈也有两种实现方式,分别为顺序存储方式和链式存储方式。下面分别用两种方式实现栈。
2.1 栈的顺序存储实现
顺序栈是用顺序存储结构实现的栈,即用一组连续的存储单元来存储栈中的元素。由于栈是一种特殊的线性表,因此可以选择在数组末尾进行栈中元素的插入和删除操作,即将数组的末尾作为栈顶。此时入栈出栈操作可以在O(1)时间内完成。
堆栈在使用过程中所需要的空间很难估计,因此设计栈时不应该设计最大存储容量。一种合理的做法是和线性表一样,为栈分配一个基本容量,在使用中不够时,再倍增数组的长度,这个操作均摊到每个元素时,不会影响栈操作的时间复杂度。
import java.util.Arrays;
/**
* 顺序栈的实现
* @author retreatweb
*
* @param <T> 栈中数据类型
*/
public class SequenceStack<T> {
//指定底层数组的默认长度
private int DEFAULT_SIZE = 10;
//指定底层数组的长度
private int capacity;
//当底层数组长度不够时每次增加的长度
private int capacityIncrement = 0;
//定义一个数组用于保存栈中的元素
private Object[] elementData;
//保存顺序栈中元素的个数
private int size = 0;
//已默认数组长度创建栈
public SequenceStack(){
capacity = DEFAULT_SIZE;
elementData = new Object[capacity];
}
//以一个初始化元素来创建顺序栈
public SequenceStack(T element){
this();
elementData[0] = element;
size++;
}
/**
* 以指定的数组长度来创建栈
* @param element 战中的第一个元素
* @param initSize 指定顺序栈底层数组的长度
*/
public SequenceStack(T element,int initSize){
this.capacity = initSize;
elementData = new Object[capacity];
elementData[0] = element;
size++;
}
/**
* 以指定长度的数组来创建顺序栈
* @param element 顺序栈中的第一个元素
* @param initSize 顺序栈的第一个元素
* @param capacityIncrement 当顺序栈底层数组长度不够时底层数组每次增加的长度
*/
public SequenceStack(T element,int initSize,int capacityIncrement){
this.capacity = initSize;
this.capacityIncrement = capacityIncrement;
elementData = new Object[capacity];
elementData[0] = element;
size++;
}
/**
* 获取顺序站的大小
* @return 返回顺序栈中元素的个数
*/
public int length(){
return size;
}
/**
* 入栈
* @param element 需要入栈的元素
*/
public void push(T element){
ensureCapacity(size + 1);
elementData[size] = element;
size++;
}
/**
* 确保底层数组的大小满足栈的需求
* @param minCapacity 栈的最小值
*/
private void ensureCapacity(int minCapacity){
//如果数组的长度小于目前所需的长度
if(capacity < minCapacity){
//当capacityIncrement大于0且数组的长度小于栈的大小时持续对底层数组扩充
if(capacityIncrement > 0){
while(capacity < minCapacity){
capacity += capacityIncrement;
}
}else{
while(capacity < minCapacity){
capacity <<= 1;
}
}
//使用java工具类Arrays中的复制方法
elementData = Arrays.copyOf(elementData,capacity);
}
}
/**
* 出栈
* @return 返回被删除元素
*/
public T pop(){
@SuppressWarnings("unchecked")
T oldValue = (T) elementData[size - 1];
//释放栈顶元素
elementData[size - 1] = null;
size --;
return oldValue;
}
/**
* 返回栈顶元素但不删除栈顶元素
* @return 返回栈顶元素
*/
@SuppressWarnings("unchecked")
public T peek(){
return (T) elementData[size - 1];
}
/**
* 判断顺序栈是否为空
* @return 根据顺序栈中元素的个数判断
*/
public boolean isEmpty(){
return size == 0;
}
/**
* 清空顺序栈
*/
public void clear(){
//将底层数组的所有元素都置为空
Arrays.fill(elementData,null);
size = 0;
}
public String toString(){
if(size == 0){
return "[]";
}else{
StringBuilder sb = new StringBuilder("[");
for (int i = size - 1; i >= 0; i--) {
sb.append(elementData[i].toString()+",");
}
int len = sb.length();
return sb.delete(len - 2, len).append("]").toString();
}
}
}
2.2 栈的链式存储实现
链栈即采用链表实现的栈。采用单链表实现栈,根据栈的操作,将链表的表头作为栈顶,此时入栈出栈等操作的时间复杂度均为O(1)。
/**
* 链栈的实现
* @author retreatweb
*
* @param <T> 链栈中元素类型
*/
public class LinkStack<T> {
/**
* 定义一个内部类,表示立链栈的节点
* @author retreatweb
*
*/
private class Node{
//保存节点的数据
private T data;
//指向下个节点的引用
private Node next;
//无参构造器
public Node(){
}
//初始化所有属性的构造器
public Node(T data, Node next) {
this.data = data;
this.next = next;
}
}
//保存栈顶元素
private Node top;
//保存栈中节点的个数
private int size;
//创建空链栈
public LinkStack(){
top = null;
size = 0;
}
//以指定的数据元素来创建链栈,该链栈只有一个元素
public LinkStack(T element){
top = new Node(element,null);
size++;
}
/**
* 返回链栈的长度
* @return 返回链栈的长度
*/
public int length(){
return size;
}
/**
* 入栈
* @param element 入栈的元素
*/
public void push(T element){
//让top 指向新创建的节点,新节点的下一个节点为原来的节点
top = new Node(element, top);
size++;
}
/**
* 出栈
* @return 返回被删除元素
*/
public T pop(){
Node oldValue = top;
//让top引用指向原栈顶元素的下一个元素
top = top.next;
//释放原栈顶元素
oldValue.next = null;
size--;
return oldValue.data;
}
/**
* 访问栈顶元素但是不删除栈顶元素
* @return 返回栈顶元素
*/
public T peek(){
return top.data;
}
/**
* 判断顺序栈是否为空
* @return 根据顺序栈中元素的个数判断
*/
public boolean isEmpty(){
return size == 0;
}
/**
* 清空顺序栈
*/
public void clear(){
top = null;
size = 0;
}
public String toString(){
if(size == 0){
return "[]";
}else{
StringBuilder sb = new StringBuilder("[");
for (Node current = top;current != null;current = current.next) {
sb.append(current.data.toString()+",");
}
int len = sb.length();
return sb.delete(len - 2, len).append("]").toString();
}
}
}
1.3 栈的两种实现方式的比较
从时间复杂度来看,两者的时间复杂度均可以达到O(1),因此顺序表提供的高效存取就没有太大的价值了,链表同样可以实现;
从空间复杂度来说,链栈需要一个额外的引用,造成空间的部分浪费,但是通常栈所需的空间是比较难估计的,因此链栈的空间利用率在大多数情况下反而比顺序栈的空间利用率要高。