栈的定义以及用java实现栈的API的两种形式–链表和数组
最近在学习算法第四版,看网课的同时,才发现之前学数据结构的时候囫囵吞枣,没有领会到其中的精髓,故对栈的相关知识进行略微总结,如有不对之处请指出,谢谢!
栈的定义
栈(stack)又名堆栈,它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。
public class StackOfStrings
------------------------------------------------------------------------
StackOfStrings //创建一个空栈
void push(String item) //往栈里压入一个数据
String pop() //弹出栈顶的数据
boolean isEmpty() //判断栈是否为空
int size() //返回栈里面元素的个数
// An highlighted block
API实现之链表
public class LinkedStackOfString{
private Node first = null;
private class Node{
String item;
Node next;
}//将node作为类的内部类
public boolean isEmpty(){
return first == null;}
public void push(String item){
Node oldfirst = first;
first = new Node();
first.item = item;
first.next = oldfirst;
}//建立一个新的结点并将它指向原本的头结点,使之成为新的头结点,完成入栈
public String pop(){
String item = first.item;
first = first.next;
return item;
}//创建一个字符串来保存头结点的值并打印,使头结点的指针指向下一个结点,完成出栈
}
性能分析:
栈的链表实现中,每个操作在最坏情况下都是常数时间,并且不存在循环;从空间上来看,在Java中,每个对象都会有16字节的额外空间,由于内部类的存在,还需要8字节的额外空间。在类Node中有两个引用,一个指向字符串,另一个指向Node类,各自需要8字节,因此,每个栈节点需要40字节的空间,如果一个栈的大小为N,由于还有对于first的引用,所以大概需要40N的空间。
API实现之数组
public class ResizingArrayStackOfString{
public ResisingArrayStackOfString(){
s = new String[1];
}
public void push(String item){
if(N == s.length) resize(2*s.length);
s[N++]=item;
}//通过resize方法来创建一个大小为原来2倍的数组,并将入栈的数组存在最后一位
private void resize(int capacity){
String[] copy = new String[capacity];
for(int i=0;i<N;i++)
copy[i]=s[i];
s=copy;
}//建立一个大小为原来capacity倍的数组,然后把原来的数组复制到其中
public String pop(){
String item = s[--N];
s[N] = null;
if(N>0 && N == s.length/4) resize(s.length/2);
return item;
}
public boolean isEmpty(){
return N == 0;}
}
性能分析:
在栈的数组实现中,使用了一种叫反复倍增的方法,在数组填满时,建立一个大小翻倍的新数组,然后把数据全都复制过去,来避免频繁地建立数组以及复制数据。这样,只有在数组元素填满的情况下,才需要去访问整个数组,从而减少所有操作的访问次数,这叫做平摊分析。
接下来考虑缩小数组,我们可以在数组元素只有一半的时候,把数组缩小一半,但如果在此时出现抖动(客户端反复交替入栈出栈)现象时,会导致数组反复翻倍减半,花费大量的时间。因此,我们在数组变为1/4满的时候再进行减半。
从空间上来看,数组实现要比链表实现更节省空间,大概是8N~32N,取决于数组有多满。
两种实现的优缺点
这两种实现,实际上是同一种API的不同实现,客户端可以进行互换使用,因此,选用哪种实现,取决于客户端的需求。对于链表实现,每个操作都只需要常数时间,但为了处理链接需要一些额外的空间,所以链表实现会慢一点;可调大小的数组实现有很好的分摊时间,所以整个过程中也许会有更快的实现。当需要迅速完成某些操作时,通常会使用链表实现。