一、栈的概述
栈是一种特殊线性表,与数组和链表最大的区别就是,栈只允许在栈顶操作,不允许在栈底操作,也就是出栈和入栈都在发生在栈顶。这就有点类似叠落在一起的盘子,每次放盘子都是从下到上一个一个的放,取盘子也是从上到下一个一个的取。如下图:
从栈的操作上我们可以看出,栈是一种“受限制”的线性表,只允许从一端插入或者删除。人栈或出栈基本的操作原则是“先进后出或后进先出”,也就是第一个进入栈的元素,只能最后一个才能出栈;最后一个进入栈的元素,总是第一个出栈,然后依次操作。
二、栈的基本操作
栈的主要操作包括入栈和出栈,当然也还有清空栈、栈顶取值、栈的容量大小。栈可以有两种实现方式,数组实现的是顺序栈,链表实现的是链式栈,下面我们用数组来演示顺序栈的实现方式:
首实现接口,包含基本栈的基本操作:
package cn.com.stack;
/**
* 栈的顶级接口
* @author Administrator
* @param <T>
*/
interface Stack<T> {
/**
* 栈是否为空
* @return
*/
boolean isEmpty();
/**
* 入栈
* @param data
*/
void push(T data);
/**
* 出栈-删除栈顶的元素
* @param data
*/
T pop();
/**
* 获取栈顶元素
* @return
*/
T peek();
/**
* 清空栈
*/
void clear();
/**
*栈的大小
*/
void size();
}
栈的具体实现:首先实现栈的顶级接口Stack<T>,声明了栈的大小、存放数据的临时数组、栈的初始化大小,同时新建了栈的两个构造方法。
package cn.com.stack;
import java.util.EmptyStackException;
/**
* 顺序栈的实现
* @author Administrator
*/
public class MyStack<T> implements Stack<T>{
private int size;//栈的容量大小
private static final int capacity=10;//默认容量为10
private T [] elementData;//存放元素的数组
/**
* 指定栈的大小
* @param capacity
*/
public MyStack(int capacity){
elementData = (T[]) new Object[capacity];
}
/**
* 无参构造方法,默认为10
*/
public MyStack(){
elementData = (T[]) new Object[this.capacity];
}
从获取栈顶元素:最后一个入栈的元素,由于栈的结构特点,栈的大小也就是栈顶元素在数组中的下标位置。
/**
* 获取栈顶元素
*/
@Override
public T peek() {
//如果栈为空直接抛错
if(isEmpty()){
throw new EmptyStackException();
};
return temp[size];
}
入栈操作:当入栈时先判断是否需要扩容,将原来的存储数据的元素扩大到原来的两倍。
/**
* 入栈,栈已满时进行扩容
*/
@Override
public void push(T data) {
//当栈以满时,进行扩容
ensureCapacity(size*2);
elementData[size++]=data;
}
/**
* 判断数据是否需要扩容
* @param index
*/
public void ensureCapacity(int capacity){
if (size == elementData.length) {
//将当前数组赋值给临时数组
Object[] elementDataArray = this.elementData;
this.elementData = (T[]) new Object[capacity];
// 把原来的数组赋值复制到新的数组中
for (int i = 0; i < size; i++) {
this.elementData[i] = (T) elementDataArray[i];
}
}
}
出栈操作:栈的大小就是栈顶元素在数组中的下标位置,在删除栈顶元素后,同时将数组大小减少至栈的大小。
/**
* 出栈操作,返回出栈的元素
*/
@Override
public T pop() {
//如果栈为空,直接抛异常
if(isEmpty()){
throw new EmptyStackException();
}
T oldData =this.elementData[size-1];
size--;//栈的大小减少1
//将存储数据的数组缩小为栈的大小
T [] elementTemp = (T[]) new Object[size];
for (int i = size-1; 0 <= i; i--){
elementTemp[i]=elementData[i];
}
elementData=elementTemp;
return oldData;
}
栈的其他操作:
/**
* 栈的大小
* @return
*/
public int size(){
return this.size;
}
/**
* 判断栈是否为空
*/
@Override
public boolean isEmpty() {
return this.size==0;
}
/**
* 清除栈元素
*/
@Override
public void clear() {
this.size=0;
}
栈的基本操作已经分析完了,普通栈从入栈和出栈的操作可以看出只涉及栈顶元素的操作,所以时间复杂度时O(1),不过在动态扩容的栈中,在需要扩容时,需要重新申请内存和搬移数据,而在出栈时,将数组进行了缩容,所以这里的动态扩容的栈的时间复杂度为O(n)。