栈的本质
栈也是一种数据呈线性排列的数据结构,不过在这种结构中,我们只能访问最新添加的数据。
⨳ 栈就像是一摞书,拿到新书时我们会把它放在书堆的最上面,取书时也只能从最上面的新书开始取。
⨳ 栈也像一摞叠在一起的盘子,我们平时放盘子的时候,都是从下往上一个一个放;取的时候,我们也是从上往下一个一个地依次取,不能从中间任意抽出。
⨳ 栈也像子弹夹,子弹夹只有一个口,最新压入的子弹,也是最开始射出的子弹。
总之,后进者先出(Last In First Out,简称 LIFO),就是典型的"栈"结构。
前文讲了数组和链表这两种不同的线性存储结构,而栈既可以用数组实现(顺序栈 ),也可以用链表实现(链式栈)。
栈主要 API 如下:
⨳ void push(E)
:入栈操作,向栈顶添加元素
⨳ E pop()
:出栈操作,从栈顶弹出元素
⨳ E peek()
:查看栈顶元素
相比数组和链表,栈就是个阉割版本,那我直接使用数组或者链表不就好了吗?为什么还要用这个"操作受限"的"栈"呢?
因为数组或链表暴露了太多的操作接口,操作上的确灵活自由,但使用时就比较不可控,比较容易出错(单一职责
)。
下面就分别使用数组和链表实现一个栈。
顺序栈 ArrayStack
java
import com.cango.array.IntArray; // 数组篇讲的代码
public class IntArrayStack {
IntArray array ;
public IntArrayStack(int capacity){
array = new IntArray(capacity);
}
public IntArrayStack(){
array = new IntArray();
}
public void push(int e){
array.add(array.getSize(),e);
}
public int pop(){
return array.remove(array.getSize()-1);
}
public int peek(){
return array.get(array.getSize()-1);
}
}
看完代码你可能会说,就这?对,将数组操作阉割一下就是栈。
如果 IntArray
支持动态扩容,用 IntArray
实现的栈也会支持动态扩容,多方便。
从上代码可以看出,用数组实现栈最好让数组尾部作为栈顶,因为栈只能操作栈顶的元素,对应到数组,就是只处理数组尾部的元素,不涉及数组其他元素的搬移。
链式栈 LinkedStack
将数组操作阉割一下就是顺序栈,接着将链表阉割一下实现链式栈:
java
import com.cango.list.IntList;
public class IntLinkedStack {
IntList list;
public IntLinkedStack(){
list = new IntList();
}
public void push(int e){
list.add(0,e);
}
public int pop(){
return list.remove(0);
}
public int peek(){
return list.get(0);
}
}
单向链表适合让链表头部作为栈顶,因为链表对头部节点的增删查最快。
Stack VS Deque
scala
package java.util;
/**
* The <code>Stack</code> class represents a last-in-first-out
* (LIFO) stack of objects. It extends class <tt>Vector</tt> with five
* operations that allow a vector to be treated as a stack. The usual
* <tt>push</tt> and <tt>pop</tt> operations are provided, as well as a
* method to <tt>peek</tt> at the top item on the stack, a method to test
* for whether the stack is <tt>empty</tt>, and a method to <tt>search</tt>
* the stack for an item and discover how far it is from the top.
* <p>
* When a stack is first created, it contains no items.
*
* <p>A more complete and consistent set of LIFO stack operations is
* provided by the {@link Deque} interface and its implementations, which
* should be used in preference to this class. For example:
* <pre> {@code
* Deque<Integer> stack = new ArrayDeque<Integer>();}</pre>
*
* @author Jonathan Payne
* @since JDK1.0
*/
public
class Stack<E> extends Vector<E>
这是 Java 版本的 Stack
,从描述上看,官方推荐使用 Deque
代替 Stack
。
为啥呢?
Stack 类对 push
、pop
方法实现很好呀,原因就是它是继承 Vector
,而不是组合 Vector
(Vector
是个线程安全的数组封装),这就导致 Stack
不仅仅有 push
、pop
方法,数组相关操作方法都有,Stack
存在的意义就是屏蔽多余的操作,现在 Stack
操作这么多,怎能不让开发者迷惑。
Java 官方推荐的写法是使用 Deque 接口:
java
Deque<Integer> stack = new ArrayDeque<Integer>();}
Deque
是双端队列的意思,也就是说可以在队列两头操作元素,那我只用一头进行插入和删除,这不就是栈了嘛。
Deque
还是暴露了多余的操作呀,是的,哪能这么办,重新构造 Stack
类吗?原来的项目怎么办?
妥协的方法就是,选择一个比较符合栈操作的类,也就是 Deque
。