前面两篇文章介绍了线性表的两种实现方式:顺序(数组)存储和链式存储。
本文介绍的栈是由线性表发展而来,可以把栈当做被限制的线性表,因为栈的定义是只能在固定的一端(栈顶)进行插入和删除操作。
本文包括以下几个内容:
是一个只能在某一端进行插入、删除操作的线性表。通常在线性表的尾端,或称栈顶。
由此我们知道栈是一个后进先出(LIFO,Last In First Out)的线性表
从栈顶插入一个元素称之为入栈(push)
从栈顶删除一个元素称之为出栈(pop)
##栈的相关操作
因为栈是一个被限制了的线性表,通过索引来插入、删除、访问等操作在栈中是不支持的。
栈的常用操作:
可以将以上几个操作定义一下几个方法
void push(T element)
T pop()
T peek()
boolean isEmpty()
int size()
void clear()
入栈、出栈操作示意图
##顺序存储实现栈
因为栈也是一个线性表,我们已经在《数据结构与算法(一)线性表之顺序存储和ArrayList、Vector实现》中实现了顺序存储的线性表,所以顺序存储来实现一个栈就很简单了。
public class Stack implements Iterable {
private static final int DEFAULT_SIZE = 10;
//数组的长度
private int capacity;
//定义当底层数据容量不够时,每次增加的数组长度
private int capacityIncrement;
//用于保存顺序栈的元素
private Object[] elementData;
//元素的个数
private int size;
public Stack() {
this.capacity = DEFAULT_SIZE;
elementData = new Object[this.capacity];
}
public Stack(T element) {
this();
elementData[0] = element;
size++;
}
public Stack(T element, int initSize) {
this.capacity = initSize;
elementData = new Object[this.capacity];
elementData[0] = element;
size++;
}
public Stack(T element, int initSize, int capacityIncrement) {
this(element, initSize);
this.capacityIncrement = capacityIncrement;
}
private void ensureCapacity(int minCapacity) {
if (minCapacity > capacity) {
if (capacityIncrement > 0) {
while (capacity < minCapacity) {
capacity += capacityIncrement;
}
} else {
//不断地capacity * 2,直到capacity大于minCapacity
while (capacity < minCapacity) {
capacity <<= 1;
}
}
elementData = Arrays.copyOf(elementData, capacity);
}
}
public int size() {
return size;
}
public void push(T element) {
ensureCapacity(size + 1);
elementData[size++] = element;
}
/**
* 返回栈顶元素,并且删除该元素
*
* @return
*/
public T pop() {
T element = (T) elementData[size - 1];
elementData[--size] = null;
return element;
}
/**
* 只返回栈顶元素,但不删除该元素
*
* @return
*/
public T peek() {
return (T) elementData[size - 1];
}
/**
* 是否是空栈
*
* @return
*/
public boolean isEmpty() {
return size == 0;
}
/**
* 清空栈内元素
*/
public void clear() {
Arrays.fill(elementData, null);
size = 0;
}
@Override
public Iterator iterator() {
return new MyIterator();
}
class MyIterator implements Iterator {
private int index = size - 1;
public boolean hasNext() {
return index >= 0;
}
public T next() {
return (T) elementData[index--];
}
}
}
代码很简单,就不赘述了。
##链式存储实现栈
public class LinkedStack implements Iterable {
//使用stackTop来记录当前栈顶的元素
private Node stackTop;
private int size;
private class Node {
T element;
Node next;
Node(T element, Node next) {
this.element = element;
this.next = next;
}
}
public void push(T element) {
stackTop = new Node(element, stackTop);
size++;
}
public T pop() {
Node oldTop = stackTop;
stackTop = stackTop.next;
//释放原栈顶元素next引用
oldTop.next = null;
size--;
return oldTop.element;
}
public T peek() {
return stackTop.element;
}
public int size() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
public void clear() {
stackTop = null;
size = 0;
}
@Override
public Iterator iterator() {
return new MyIterator();
}
private class MyIterator implements Iterator {
private Node current = stackTop;
public boolean hasNext() {
return current != null;
}
public T next() {
T element = current.element;
current = current.next;
return element;
}
}
}
这里的stackTop就相当于以前介绍链表时候的tail。
Java中的Stack底层是用数组实现的,也就是通过顺序存储的方式实现的 。继承自Vector类,所以它是线程安全的的。
我们前面介绍LinkedList的说到,java.util.LinkedList是一个不仅实现了List接口,还是实现了Deque接口,也就是说LinkedList不仅是链式存储的线性表还是一个双端队列。
当然也就可以当做栈来使用,LinkedList是链式实现的栈,LinkedList是非线程安全的。
在Java中,除了Stack和LinkedList外,ArrayDeque也可以作为栈使用,ArrayDeque和LinkedList一样,也是双端队列,一个是基于数组实现的,一个是基于链表实现的。
在计算机中栈的应用非常广泛,比如实现递归计算机需要用栈来存储(关于递归后面会单独用一篇文章来分析)。比如Android界面的管理也用栈来实现。下面使用栈来解决几个算法问题。
描述如下:
给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串,判断字符串是否有效。
有效字符串需满足:
这个问题通过Stack很好解决,先把输入的字符串转化成字符数组, 遍历字符数组,如果当前字符是左括号 ‘(’、’{’、’[’ 都push到栈中,如果当前字符是 ‘)’ 则pop栈顶元素是否是 ‘)’ ,如果当前字符是 ‘}’ 则pop栈顶元素是否是 ‘{’, 如果当前字符是 ‘]’ 则pop栈顶元素是否是 ‘[’,具体代码如下:
public boolean isValid(String s) {
Stack stack = new Stack<>();
char[] cs = s.toCharArray();
for (Character c : cs) {
switch (c) {
case '(':
case '{':
case '[':
stack.push(c);
break;
case ')':
if (stack.isEmpty() || stack.pop() != '(')
return false;
break;
case '}':
if (stack.isEmpty() || stack.pop() != '{')
return false;
break;
case ']':
if (stack.isEmpty() || stack.pop() != '[')
return false;
break;
}
}
return stack.isEmpty();
}
###Dijkstra双栈算术表达式求值
在上面LeetCode的题目的基础上,我们进一步看下下面的 问题
求出表达式的值:(1+((2+3)(410)))
我们可以使用两个栈来完成这个操作,一个栈用来存储数值的,一个栈用来存储操作符的,如果遇到 ‘)’,则从值栈中取出两个值,从操作符栈中取出一个操作符,然后对这两个值进行计算,把结果在放进值栈中。这是Dijkstra的双栈算术表达式求值法。
为了简单区间,我们把数字和操作符用空格分开,这样便于我们把字符串进行分割。代码如下:
private static double evaluate(String express) {
Stack ops = new Stack<>();
Stack values = new Stack<>();
String[] cs = express.split(" ");
for (String s : cs) {
switch (s) {
case " ":
case "(":
break;
case "+":
case "-":
case "*":
case "/":
ops.push(s);
break;
case ")":
String op = ops.pop();
double v = values.pop();
switch (op) {
case "+":
v = values.pop() + v;
break;
case "-":
v = values.pop() - v;
break;
case "*":
v = values.pop() * v;
break;
case "/":
v = values.pop() / v;
break;
}
values.push(v);
break;
default:
values.push(Double.parseDouble(s));
}
}
return values.pop();
}
//测试
public static void main(String[] args) {
double value = evaluate("( 1 + ( ( 2 + 3 ) * ( 4 * 10 ) ) )");
System.out.println(value);//201.0
}
下面是我的公众号,干货文章不错过,有需要的可以关注下,有任何问题可以联系我:
https://github.com/chiclaim/BookCodePractise