详解栈Stack

栈是限定仅在表尾进行插入或删除操作得线性表。从数据结构来看,栈也是一种线性结构。但由于其操作只能在表尾完成,因此是操作受限的线性表,也可称为限定性的数据结构。

栈的逻辑定义决定了栈具备了后进先出(last in first out,FILO)的明显特征,基于该特征,栈在计算机世界中有很多应用场景。

  • 符号匹配
  • 中缀表达式转换为后缀表达式
  • 计算后缀表达式
  • 实现函数的嵌套调用
  • HTML 和 XML 文件中的标签匹配
  • 网页浏览器中已访问页面的历史记录
  • Java 中的程序计数栈、堆栈
  • word的撤销操作

栈的操作逻辑决定了这种数据结构的ADT不会太复杂,其操作有构造方法、出栈、入栈、查看栈顶、获取容量、判空。下文将介绍使用java语言实现基于数组的栈和JDK1.8中提供的java.util.Stack类。

基于数组的Stack

由于栈是一种线性结构,很自然的考虑其存储数据的底层容器可以使用数组。使用数组实现栈逻辑时,数组的尾部是栈顶。栈的操作都在数组尾部完成,注意,此处的尾部指数组有值部分的最大索引位置,不是数组长度的尾部。

首先使用java中的接口,描述栈的所有操作。这可认为是将栈的ADT转换为接口形式。

public interface Stack<E> {
    //获取栈先有大小,不是栈的容量
    int getSize();
    //出栈
    E pop();
    //入栈
    void push(E e);
    //获取栈顶元素
    E peek();
    //判空
    boolean isEmpty();
}

使用动态数组实现Stack接口,其中动态数组的实现参考实现java动态数组

/**
 * 该类的各个方法的时间复杂度都是O(1),
 * 其中 push 和 pop有可能触发resize,但经过均摊后,仍是O(1)
 * @param <E>
 */
public class ArrayStack<E> implements Stack<E> {
    Array<E> arr;
    public ArrayStack(int Capacity) {
        arr = new Array<>(Capacity);
    }
    public ArrayStack(){
        arr = new Array<>();
    }

    public int getCapacity(){
        return arr.getCapacity();
    }
    
    @Override
    public int getSize() {
        return arr.getSize();
    }
    
    @Override
    public E pop() {
        return arr.remove(arr.getSize()-1);
    }

    @Override
    public void push(E e) {
        arr.addLast(e);
    }

    @Override
    public E peek() {
        return arr.getLast();
    }

    @Override
    public boolean isEmpty() {
        return arr.isEmpty();
    }

    @Override
    public String toString(){
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("stack: [");
        for (int i = 0; i < arr.getSize(); i++) {
            stringBuilder.append(arr.get(i));
            if(i == arr.getSize()-1){
                stringBuilder.append("] top");
            }
        }
        return stringBuilder.toString();
    }
}

java.util.Stack<>

在java体系中,Stack 是 JDK 1.0 的产物,它继承自 Vector。基于 Vector 实现的栈 Stack。底层实际上还是数组,所以还是存在需要扩容。Vector 是由数组实现的集合类,它包含了大量集合处理的方法。而 Stack 之所以继承 Vector,是为了复用 Vector 中的方法,来实现进栈(push)、出栈(pop)等操作。这里就是 Stack 设计不好的地方,既然只是为了实现栈,不用链表来单独实现,而是为了复用简单的方法而迫使它继承 Vector,Stack 和 Vector 本来是毫无关系的。这使得 Stack 在基于数组实现上效率受影响,另外因为继承 Vector 类,Stack 可以复用 Vector 大量方法,这使得 Stack 在设计上不严谨。

在jdk1.8中,提供了

  • push() 入栈,返货入栈值

  • pop() :出栈,返回出栈值

  • peek() :获取栈顶元素

  • empty() :判断栈空

  • search(Object):以栈顶为1开始计数,返回元素位置

在日常代码实践中,由于性能问题,Vector已不被推荐使用,因此Vector的子类Stack也不再推荐使用,推荐使用Deque(Double Ended Queue,双端队列)。

若要使用java.util.Stack,只需记住该类提供的接口名称。同时,注意该类是一个泛型类,创建对象时注意传入类型,否则报错“Raw use of parameterized class ‘xxxx‘”

延申

  • 对于一种数据结构,向用户提供的是逻辑功能,用户不会关心这种数据结构的底层实现。譬如栈,用户只需用栈后进先出的特点满足自己的业务需求,而不关心底层是怎么实现后进先出。用户关心的是数据结构的某一功能的输入输出。
  • 各种数据结构,不管是队列,栈等线性数据结构还是树,图的等非线性数据结构,从根本上底层都是数组和链表。不管你用的是数组还是链表,用的都是计算机内存,物理内存是一个个大小相同的内存单元构成的

参考资料

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值