一、引言
栈是一种后进先出(Last in First out LIFO)的线性数据结构,在此简单用动态数组实现一个栈结构。栈结构虽简单,但却非常有用,应用诸如编辑器的撤销操作、程序的方法调用栈等。
二、实现
1、基于动态数组实现栈
- 动态数组类
/**
* 动态数组,数组二次封装
*/
public class Array<E> {
/**
* 基于Java原生数组,保存数据的容器
*/
private E[] data;
/**
* 当前元素个数
*/
private int size;
public Array(int capacity) {
data = (E[]) new Object[capacity];
size = 0;
}
/**
* 默认数组容量capacity=10
*/
public Array() {
this(10);
}
/**
* 获取数组中元素个数
* @return
*/
public int getSize() {
return size;
}
/**
* 获取数组的容量
* @return
*/
public int getCapacity() {
return data.length;
}
/**
* 判断数组是否为空
* @return
*/
public boolean isEmpty() {
return size == 0;
}
/**
* 在所有元素后面添加新元素
* @param e 元素
*/
public void addLast(E e) {
add(size, e);
}
/**
* 在所有元素前面添加新元素
* @param e 元素
*/
public void addFirst(E e) {
add(0, e);
}
/**
* 向index索引位置插入一个新元素e
* @param index 数组索引位置
* @param e 元素
*/
public void add(int index, E e) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("addList failed. index < 0 || index > size");
}
//空间不足,扩容
if (size == data.length) {
resize(2 * data.length);
}
for (int i = size - 1; i >= index; i--) {
data[i + 1] = data[i];
}
data[index] = e;
size++;
}
/**
* 根据元素索引获取数组元素
* @param index 索引
* @return
*/
public E get(int index) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("get failed. index is illegal");
}
return data[index];
}
/**
* 获取数组最后一个元素
* @return
*/
public E getLast() {
return get(size - 1);
}
/**
* 获取数组第一个元素
* @return
*/
public E getFirst() {
return get(0);
}
/**
* 根据元素索引修改数组元素
* @param index 索引
* @param e 元素
* @return
*/
public void set(int index, E e) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("get failed. index is illegal");
}
data[index] = e;
}
/**
* 判断包含元素
* @param e 元素
* @return
*/
public boolean contains(E e) {
for (int i = 0; i < size; i++) {
if (data[i].equals(e)) {
return true;
}
}
return false;
}
/**
* 查找元素索引
* @param e 元素
* @return 返回元素索引,如果不存在则返回-1
*/
public int find(E e) {
for (int i = 0; i < size; i++) {
if (data[i].equals(e)) {
return i;
}
}
return -1;
}
/**
* 移除指定索引的元素
* @param index 索引
* @return 返回被移除的元素
*/
public E remove(int index) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("get failed. index is illegal");
}
E ret = data[index];
for (int i = index + 1; i < size; i++) {
data[i - 1] = data[i];
}
size--;
data[size] = null;
//空间利用率低,数组缩容,防止复杂度震荡
if (size == data.length / 4 && data.length / 2 != 0) {
resize(data.length / 2);
}
return ret;
}
/**
* 移除第一个元素
* @return 返回被移除元素
*/
public E removeFirst() {
return remove(0);
}
/**
* 移除最后一个元素
* @return 返回被移除元素
*/
public E removeLast() {
return remove(size - 1);
}
/**
* 移除数组中一个元素
* @param e 元素
*/
public void removeElement(E e) {
int index = find(e);
if (index != -1) {
remove(index);
}
}
/**
* 数组容器扩容、缩容
* @param newCapacity 新的容量
*/
private void resize(int newCapacity) {
E[] newData = (E[]) new Object[newCapacity];
for (int i = 0; i < size; i++) {
newData[i] = data[i];
}
data = newData;
}
@Override
public String toString() {
StringBuilder res = new StringBuilder();
res.append(String.format("Array: size = %d, capacity = %d\n", size, data.length));
res.append("[");
for (int i = 0; i < size; i++) {
res.append(data[i]);
if (i != size - 1) {
res.append(", ");
}
}
res.append("]");
return res.toString();
}
}
- 栈类接口
public interface Stack<E> {
/**
* 获取栈中元素个数
* @return
*/
int getSize();
/**
* 判断栈中是否没有元素
* @return
*/
boolean isEmpty();
/**
* 入栈
* @param e 元素
*/
void push(E e);
/**
* 出栈
* @return 弹出元素
*/
E pop();
/**
* 查看栈顶元素
* @return
*/
E peek();
}
- 栈类实现
/**
* 基于动态数组实现栈
* @param <E>
*/
public class ArrayStack<E> implements Stack<E> {
private Array<E> array;
public ArrayStack(int capacity) {
array = new Array<>(capacity);
}
public ArrayStack() {
array = new Array<>();
}
/**
* 获取栈中元素个数
* @return
*/
@Override
public int getSize() {
return array.getSize();
}
/**
* 判断栈中是否没有元素
* @return
*/
@Override
public boolean isEmpty() {
return array.isEmpty();
}
/**
* 获取容量
* @return
*/
public int getCapacity() {
return array.getCapacity();
}
/**
* 入栈
* @param e 元素
*/
@Override
public void push(E e) {
array.addLast(e);
}
/**
* 出栈
* @return 弹出元素
*/
@Override
public E pop() {
return array.removeLast();
}
/**
* 查看栈顶元素
* @return
*/
@Override
public E peek() {
return array.getLast();
}
@Override
public String toString() {
StringBuilder res = new StringBuilder();
res.append("Stack: ");
res.append("[");
for (int i = 0; i < array.getSize(); i++) {
res.append(array.get(i));
if (i != array.getSize() - 1) {
res.append(", ");
}
}
res.append("] top");
return res.toString();
}
}
2、基于链表实现栈
- 链表实现
/**
* 链表实现
* @param <E>
*/
public class LinkedList<E> {
/**
* 虚拟头节点
*/
private Node dummyHead;
/**
* 链表中元素个数
*/
private int size;
public LinkedList() {
dummyHead = new Node(null,null);
size = 0;
}
/**
* 获取链表中元素个数
* @return
*/
public int getSize() {
return size;
}
/**
* 判断链表是否为空
* @return
*/
public boolean isEmpty() {
return size == 0;
}
/**
* 在链表的index位置添加新的元素
* 在链表中不是一个常用的操作,练习用
* @param index 索引位置
* @param e 元素
*/
public void add(int index, E e) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("add failed. illegal index.");
}
//找到插入位置的前一个节点
Node prev = dummyHead;
for (int i = 0; i < index; i++) {
prev = prev.next;
}
//插入元素
prev.next = new Node(e, prev.next);
size++;
}
/**
* 在链表头添加元素
* @param e 元素
*/
public void addFirst(E e) {
add(0, e);
}
/**
* 在链表末尾添加新的元素
* @param e 元素
*/
public void addLast(E e) {
add(size, e);
}
/**
* 获取链表指定位置的元素
* 非常用操作,练习用
* @param index 指定位置
* @return 元素
*/
public E get(int index) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("get failed, illegal index.");
}
Node cur = dummyHead.next;
for (int i = 0; i < index; i++) {
cur = cur.next;
}
return cur.e;
}
/**
* 获取链表第一个元素
* @return
*/
public E getFirst() {
return get(0);
}
/**
* 获取链表最后一个元素
* @return
*/
public E getLast() {
return get(size - 1);
}
/**
* 更新链表指定位置的元素
* 非常用操作,练习用
* @param index 指定位置
* @return 元素
*/
public void set(int index, E e) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("set failed, illegal index.");
}
Node cur = dummyHead.next;
for (int i = 0; i < index; i++) {
cur = cur.next;
}
cur.e = e;
}
/**
* 查找链表中是否存在元素e
* @param e 元素
* @return
*/
public boolean contains(E e) {
Node cur = dummyHead.next;
while (cur != null) {
if (cur.e.equals(e)) {
return true;
}
cur = cur.next;
}
return false;
}
/**
* 删除链表指定位置的元素
* 非常用操作,练习用
* @param index 指定位置
* @return 元素
*/
public E remove(int index) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("remove failed, illegal index.");
}
Node prev = dummyHead;
for (int i = 0; i < index; i++) {
prev = prev.next;
}
Node removeNode = prev.next;
prev.next = removeNode.next;
removeNode.next = null;
size--;
return removeNode.e;
}
/**
* 移除头部元素
* @return
*/
public E removeFirst() {
return remove(0);
}
public E removeLast() {
return remove(size - 1);
}
@Override
public String toString() {
StringBuilder res = new StringBuilder();
Node cur = dummyHead.next;
while (cur != null) {
res.append(cur + "->");
cur = cur.next;
}
res.append("NULL");
return res.toString();
}
/**
* 节点
*/
private class Node {
public E e;
public Node next;
public Node(E e,Node next) {
this.e = e;
this.next = next;
}
public Node(E e) {
this(e, null);
}
public Node() {
this(null, null);
}
@Override
public String toString() {
return e.toString();
}
}
}
- 链表栈实现
/**
* 基于链表实现栈
* @param <E>
*/
public class LinkedListStack<E> implements Stack<E> {
/**
* 链表容器
*/
private LinkedList<E> list;
public LinkedListStack() {
list = new LinkedList<>();
}
/**
* 获取栈中元素个数
* @return
*/
@Override
public int getSize() {
return list.getSize();
}
/**
* 判断栈中是否有元素
* @return
*/
@Override
public boolean isEmpty() {
return list.isEmpty();
}
/**
* 入栈操作
* @param e 元素
*/
@Override
public void push(E e) {
list.addFirst(e);
}
/**
* 出栈操作
* @return
*/
@Override
public E pop() {
return list.removeFirst();
}
/**
* 查看栈顶元素
* @return
*/
@Override
public E peek() {
return list.getFirst();
}
@Override
public String toString() {
StringBuilder res = new StringBuilder();
res.append("stack: top ");
res.append(list);
return res.toString();
}
}
三、性能测试
- 简单本地测试
import java.util.Random;
public class Main {
public static void main(String[] args) {
int opCount = 10000000;
LinkedListStack<Integer> linkedListStack = new LinkedListStack<>();
double linkedListStackTime = testStack(linkedListStack, opCount);
System.out.println("LinkedListStack, time:" + linkedListStackTime + "s");//LinkedListStack, time:8.1041194s
ArrayStack<Integer> arrayStack = new ArrayStack<>();
double arrayStackTime = testStack(arrayStack, opCount);
System.out.println("ArrayStack, time:" + arrayStackTime + "s");//ArrayStack, time:0.5274266s
}
/**
* 测试使用栈运行入栈、出栈操作所需的时间
* @param stack 待测试栈
* @param opCount 操作次数
* @return
*/
private static double testStack(Stack<Integer> stack, int opCount) {
long statTime = System.nanoTime();
Random random = new Random();
for (int i = 0; i < opCount; i++) {
stack.push(random.nextInt(Integer.MAX_VALUE));
}
for (int i = 0; i < opCount; i++) {
stack.pop();
}
long endTime = System.nanoTime();
return (endTime - statTime) / 1000000000.0;
}
}
- 说明:无论基于动态数组还是链表实现的栈,入栈、出栈的时间复杂度在同一个级别O(1)。二者的不同的在于,基于数组实现的栈会出现数组的扩缩容,这是它的缺点;而基于链表实现的栈,入栈、出栈操作会在创建更多的对象。以上不太严谨的测试可以看出,二者各执行10000000万次操作时,基于数组的实现性能要好与基于链表的实现。
四、栈的应用
1、括号匹配
public class Solution {
/**
* 括号匹配检验
* @param s 包含括号的字符串,例如:{(){}[]}
* @return
*/
public boolean isValid(String s) {
ArrayStack<Character> stack = new ArrayStack<>();
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
//左括号入栈
if (c == '(' || c == '[' || c == '{') {
stack.push(c);
} else if (c == ')' || c == ']' || c == '}'){
if (stack.isEmpty()) {
return false;
}
//取栈顶左括号比较
char topChar = stack.pop();
if (topChar == '(' && c != ')') {
return false;
}
if (topChar == '[' && c != ']') {
return false;
}
if (topChar == '{' && c != '}') {
return false;
}
}
}
//最终栈必须为空,否则括号匹配失败
return stack.isEmpty();
}
}
五、复杂度分析
1、基于数组实现的复杂度分析
- push(e) => O(1) : 虽然push操作有可能触发动态数组的resize扩容操作,但是均摊下来,很接近O(1)。
- pop() => O(1) : 同样pop操作有可能触发动态数组的resize缩容操作,但是均摊下来,很接近O(1)。
- peeK() => O(1) : 查看栈顶元素与栈中元素个数无关
- getSize() => O(1) : 获取栈中元素个数实际上获取的是动态数组的游标,时间复杂度同样与栈中元素个数无关。
- isEmpty() => O(1) : 同getSize
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。