在数据结构之堆栈的顺序存储一文中,讲述了堆栈的顺序存储,并指出了顺序存储的局限性。本文讲述堆栈的另一种存储方式:链式存储。
堆栈的链式存储(链栈)是链表的一种表现形式,依然遵循堆栈的“先进后出”这一基本原则。链栈的最小单元是结点,一个结点包含数据域和指针域两个部分。链式存储结构如下图:
图a:堆栈中有一个结点,数据域为A,指针域为null,此时top指向该结点;
图b:向链栈中压入数据域为B的结点,此时堆栈中有两个结点,数据域为A的结点为栈底,数据域为B的结点为栈顶,此时top指向栈顶;
图c:向链栈中压入数据域为C的结点,此时堆栈中有三个结点,数据域为A的结点为栈底,数据域为C的结点为栈顶,此时top指向栈顶;
图d:从栈中弹出数据域为C的栈顶元素,并将top指向结点C的指针域指向的结点B;
图e:从栈中弹出数据域为B的栈顶元素,并将top指向结点B的指针域指向的结点A。
按照面向对象设计的原则,链栈中包括两个对象:一个为结点对象,一个为栈对象。
结点类如下:
public class Node {
private Object data;
private Node next;
//构造函数
public Node() {
this.data = null;
this.next = null;
}
//构造函数重载
public Node(Object data) {
this.data = data;
this.next = null;
}
public Node(Object data, Node next) {
this.data = data;
this.next = next;
}
//读结点数据
public Object getData() {
return data;
}
//写结点数据
public void setData(Object data) {
this.data = data;
}
//获取结点链接的下一个点
public Node getNext() {
return next;
}
//设置结点链接的下一个点
public void setNext(Node next) {
this.next = next;
}
}
链栈类如下:包含判断链栈是否为空、压入元素、弹出栈顶元素、获取栈顶元素等操作。由于链栈可以根据元素数目动态分配内存空间,链栈可以看做有无穷多个空间,除非内存满了。因此,在链栈类中并没有定义判断栈满的操作。
public class LinkHeap {
/**
* 定义并初始化栈顶结点
*/
private Node topNode = null;
/**
* 判断链栈是否为空栈
*
* @return 如果链栈为空栈,返回true;否则返回false
*/
public boolean isEmpty() {
return topNode == null;
}
/**
* 将数据压入链栈
*
* @param data 压入链栈的数据
*/
public void push(Object data) {
Node p = new Node(data, topNode);
topNode = p;
}
/**
* 弹出栈顶元素
*
* @return 如果链栈为空栈,返回空值;否则返回栈顶结点的数据域
*/
public Object pop() {
if(isEmpty()) {
return null;
}
else {
Object data = topNode.getData();
topNode = topNode.getNext();
return data;
}
}
/**
* 获取链栈的栈顶元素
*
* @return 如果链栈为空栈,返回空值;否则返回栈顶结点的数据域
*/
public Object get() {
return isEmpty() ? null : topNode.getData();
}
}
链栈的操作的时间复杂度很低,上述四种方法的时间复杂度均为O(1)。