Java容器&数据结构——栈
“栈”通常指“后进先出”的(LIFO)容器。本文将分别介绍基于LinkedList和不依赖LinkedList的两种实现方法。
友情提示:这里,我假设读者已经了解泛型,内部类,LinkedList容器以及递归的概念,如果没有,可能会对您的理解造成一点不便。
一、基于LinkedList的实现
在Java中,LinkedList具有能够实现栈的所有功能的方法,因此可以将LinkedList作为栈使用,实现类可以非常的优雅而简洁:
package com.test.collection; import java.util.LinkedList; public class Stack<T> { private LinkedList<T> storage = new LinkedList<T>(); public void push(T v) { storage.addFirst(v); } public T peek() { return storage.getFirst(); } public T pop() { return storage.removeFirst(); } public boolean empty() { return storage.isEmpty(); } public String toString() { return storage.toString(); } }
通过使用泛型,引入了栈在类定义中最简单的可行示例。push()接受T类型对象并压入栈中,而peek()和pop()将返回T类型的对象。区别是前者仅返回栈顶元素,后者返回并移除栈顶元素。
如果你只需要栈的行为,这里使用继承就不合适了,因为这样会产生具有LinkedList其他所有方法的类(据说,Java1.0设计者在设计Java.util.Stack时,就犯了这个错误)。因此尽管已经有了java.util.Stack,但是LinkedList可以产生更好的Stack,下面的测试类中同时测试自己实现的Stack和Java.util.Stack里的Stack
package com.test.collection; public class StackCollision { public static void main(String[] args) { com.test.collection.Stack<String> stack = new com.test.collection.Stack<String>(); for(String s : "My dog has fleats".split(" ")) stack.push(s); while(!stack.empty()) System.out.print(stack.pop() + " "); System.out.println(); java.util.Stack<String> stack2 = new java.util.Stack<String>(); for(String s : "My dog has fleats".split(" ")) stack2.push(s); while(!stack2.empty()) System.out.print(stack2.pop() + " "); } }
测试结果:
这里创建Stack实例时,需要完整地指定包名,否则有可能与java.util包中的Stack产生冲突。这两个Stack具有相同的接口,但是java.util中没有任何公共的Srack接口。
二、不依赖于LinkedList的实现
如上面的例子所示,LinkedList本身已经具备了创建堆栈所必须的方法。我们只需要将LinkedList已有的方法进行封装,就可以完成一个堆栈的实现了。但是如果我觉得这样不利于我深入理解堆栈的实现,不想调用LinkedList的方法,想自己从头实现,该怎么办呢?下面的例子或许可以给你提供一点思路:
1 package com.test.generic; 2 3 public class LinkedStack<T> { 4 private class Node<U> { 5 U item; 6 Node<U> next; 7 Node() { 8 item = null; 9 next = null; 10 } 11 Node(U item, Node<U> next) { 12 this.item = item; 13 this.next = next; 14 } 15 boolean end() { 16 return item == null && next == null; 17 } 18 } 19 private Node<T> top = new Node<T>();//End sentiel 末端哨兵 20 public void push(T item) { 21 top = new Node<T>(item, top); 22 } 23 public T pop() { 24 T result = top.item; 25 //item和next(节点参数)任意一个非null,则查找下一个节点,若item和next 26 //的值均为null,则不再查找下一个节点,直接返回null的result = top.item 27 if(!top.end()) 28 top = top.next; 29 return result; 30 } 31 32 public static void main(String[] args) { 33 LinkedStack<String> lss = new LinkedStack<String>(); 34 for(String s : "Phasers on stun!".split(" ")) 35 lss.push(s); 36 String s; 37 while((s = lss.pop()) != null) 38 System.out.println(s); 39 } 40 }
上例使用了一个带泛型参数的内部类Node<U>作为堆栈节点,参数item为泛型,用于接收节点上各种类型的对象或者基本类型;类型为next作为节点参数,接收下一个节点的信息;top为末端哨兵(end sential)的一个例子,与前两个参数配合,实现判断堆栈何时为空。这个末端哨兵实在构造LinkedStack时创建的,然后,每调用一次push()方法,就会创建一个Node<T>对象。调用pop()方法时,总是返回top.item,然后舍弃当前top所指的Node<T>,并将top转移到下一个Node<T>,碰到末端哨兵,就不在移动top,因为如果到了末端,再继续调用pop方法,只能得到null,说明堆栈已经空了。
输出结果中我们可以看到,被顺序装入的字符串"Phasers","on","stun!"被倒序的输出,符合堆栈“后进先出”(LIFO)的特性。
最后,感谢Bruce Eckel提供的例子。