栈(Stack)
栈是一种特殊的线性表,仅能在线性表的一端操作,栈顶允许操作,栈底不允许操作,栈的重要特点为:后进先出或先进后出。
栈的特点:先进后出(FILO,First In Last Out),我们也称之为FILO表。
Java中有一个类Stack,用于表示栈,但这个类已经过时了。目前,如果需要使用栈,那么应该使用Deque的接口来实现,Deque的接口的实现类是LinkedList类。
Java中没有单独的栈接口,栈相关方法包括在了表示双端队列的接口Deque中,主要有四个方法:
方法名 | 描述 |
void push(E e); | 入栈,在头部添加元素,栈的空间可能是有限的,如果栈满了,会抛出异常。 |
E pop(); | 出栈,返回头部元素,并且从栈中删除,如果栈为空,会抛出异常。 |
E peek(); | 查看栈头部元素,不修改栈,如果栈为空,返回null。 |
boolean isEmpty(); | 判断栈是否为空,如果为空则返回true;否则返回false。 |
【示例】使用LinkedList实现栈操作:
public class Test {
public static void main(String[] args) {
// 栈,先进后出
Deque deque = new LinkedList();
// 压栈,在栈顶添加元素
deque.push(111);
deque.push(222);
deque.push(333);
deque.push(444);
// 遍历栈中的所有元素,直到栈为空为止
while(!deque.isEmpty()) {
// 出栈,获取栈顶元素
System.out.println(deque.pop());
}
}
}
每个栈都有一个栈顶指针,它初始值为-1,且总是指向最后一个入栈的元素,栈有两种处理方式,即进栈(push)和出栈(pop),因为在进栈只需要移动一个变量存储空间,所以它的时间复杂度为O(1),但是对于出栈分两种情况,栈未满时,时间复杂度也为O(1),但是当栈满时,需要重新分配内存,并移动栈内所有数据,所以此时的时间复杂度为O(n)。以下举例栈结构的两种实现方式,顺序表存储和链表存储。
数组模拟栈实现:
要求:需要限制栈的最大容量。
package com.whsxt.stack;
/**
* 数组模拟栈(限制最大容量)
*/
public class ArrayStack<T> {
/**
* 存放数据的容器
*/
private Object[] elementData;
/**
* 指向栈顶的指针
*/
private int top = -1; // 默认值为-1
/**
* 保存栈的最大容量
*/
private int maxSize;
/**
* 构造方法
* @param maxSize 设置栈的最大容量
*/
public ArrayStack(int maxSize) {
this.elementData = new Object[maxSize];
this.maxSize = maxSize;
}
/**
* 入栈操作
* @param value
*/
public void push(T element) {
// 1.判断栈是否已满
if(isFull()) {
throw new RuntimeException("栈已满,无法执行入栈操作");
}
// 2.添加数据入栈
elementData[++top] = element;
}
/**
* 出栈操作
*/
public T pop() {
// 1.判断栈是否为空
if(isEmpty()) {
throw new RuntimeException("栈为空,无执行出栈操作");
}
// 2.获取需要出栈的元素
T value = (T)elementData[top];
// 3.清空栈顶存放的数据
elementData[top] = null;
// 4.修改栈顶指针的指向
top--;
// 6.返回需要取出的栈顶元素
return value;
}
/**
* 检查栈是否为空
* @return
*/
public boolean isEmpty() {
return top == -1;
}
/**
* 判断栈是否已满
* @return
*/
private boolean isFull() {
return maxSize - 1 == top;
}
}
链表模拟栈实现:
要求:无需限制链表的最大容量。
package com.whsxt.p3.demo;
/**
* 链表模拟栈(不限制最大容量)
*/
public class LinkedListStack<T> {
/**
* 保存单链表首节点
*/
private StackNode<T> headNode;
/**
* 保存实际添加元素的个数
*/
private int size;
/**
* 入栈操作(链表的头部开始插入)
* @param element 需要入栈的数据
*/
public void push(T element) {
// 处理链表为空的情况
if(headNode == null) {
// 把需要入栈的数据封装成节点对象,并设置为链表的首节点
headNode = new StackNode<>(element, null);
}
// 处理链表不为null的情况
else {
// 把需要入栈的数据封装成节点对象,其next指向headNode
StackNode<T> node = new StackNode<>(element, headNode);
// 更新headNode节点
headNode = node;
}
// 4.实际存放元素个数递增
size++;
}
/**
* 出栈操作(删除单链表首节点)
* @return 返回栈顶的数据
*/
public T pop() {
// 判断栈是否为空
if(isEmpty()) {
throw new RuntimeException("栈为空,无执行出栈操作");
}
// 定义tempNode变量,来零时保存headNode节点
StackNode<T> tempNode = headNode;
// 更新headNode的值,让其指向下一个节点
headNode = headNode.next;
// 完成出栈操作后,size递减
size--;
// 返回栈顶的数据
return tempNode.data;
}
/**
* 获取栈顶元素(不会删除栈顶元素)
* @return
*/
public T peek() {
// 判断栈是否为空
if (isEmpty()) {
throw new RuntimeException("栈为空,无执行出栈操作");
}
// 返回栈顶数据
return headNode.data;
}
/**
* 判断栈是否为空
* @return 如果栈为空,则返回true;如果栈非空,则返回false。
*/
public boolean isEmpty() {
return size == 0;
}
/**
* 栈节点类
*/
private static class StackNode<T> {
/**
* 节点中存放的数据
*/
private T data;
/**
* 指向下一个节点
*/
private StackNode<T> next;
/**
* 构造方法
* @param data
*/
public StackNode(T data, StackNode<T> next) {
this.data = data;
this.next = next;
}
}
}