[Java] Stack(栈) 韩顺平数据结构系列作业

        栈(Stack)是一种抽象数据类型,有数组(Array)和链表(LinkedList)两种实现方式。

        栈的常用方法包括入栈(push)、出栈(pop)、清空(clear),以及相关辅助方法,如显示数据(print)、判断是否为空(isEmpty)等,用数组实现的栈还需要判断是否已满(isFull)和扩大容量(resize)方法。

数组实现栈(Stack backed by Array)

      基本思路:栈是一种“后入先出”(Last IFirst Out, LIFO)的模型。在入栈时,符合“后来者居上”的规则,后添加的数据放在先添加的数据之上;在出栈时,数据自上而下被逐个取出,所以后添加的数据先取出(即入栈和出栈均作用于栈的同一端)。栈顶(top)指向最后添加的数据,栈底(bottom)指向最初添加的数据。

      如果用数组实现栈,则数组的头在栈底,数组的尾在栈顶。入栈时,新的数据添加在数组的尾部(即栈顶),数组不断向上扩展。但是,需注意数组的大小是既定的,必要时需要对其扩容,因此需要定义isFull()方法判断数组是否已满,和resize()方法用于调整大小。出栈时,提取数组末端的数据即可,同时,也注意到“数组为空”的边界情况时,没有数据可以提取,因此需要定义一个isEmpty()方法来判断是否为空数组。

      在显示数据时,如果想正确反映栈的结构(即第一行打印的是最后一个数据,第二行是倒数第二个数据,以此类推,最后一行打印的是第一个数据),应当先打印“后添加的数据”,即应当从后向前遍历数组。关于clear()方法的实现方式,文末详述。

class ArrayStack {
    private int size;         // 数组大小
    private int[] stack;      // 数组
    private int top = -1;     // 栈顶在数组为空时,值为-1

    public ArrayStack(int initialSize) {
        // 保证数组大小为正
        if (initialSize <= 0)
            throw new RuntimeException("The size should be positive");
        
        this.size = initialSize;     
        stack = new int[initialSize];
    }

    // // 通过栈顶所在位置判断数组是否已满或为空
    public boolean isFull() { return top == size - 1; }   

    public boolean isEmpty() {
        return top == -1;
    }

    public void push(int value) {
        // 如果数组已满,先调整大小
        if (isFull()) {
            resize();
        }

        top++;
        stack[top] = value;
    }

    public int pop() {
        if (isEmpty()) {
            throw new NoSuchElementException("The stack is empty now");
        }

        int value = stack[top--];
        return value;
    }

    public void print() {
        if (isEmpty()) {
            System.out.println("The stack is empty now");
            return;
        }

        for (int i=top; i>=0; i--) {
            System.out.printf("stack[%d] = %d\n", i, stack[i]);
        }
    }

    public void resize() {
        // 定义一个大小为原数组两倍的新数组
        int[] newArray = new int[size * 2];

        // 复制原数组的数据至新数组
        for (int i=0; i<size; i++)
            newArray[i] = stack[i];
        stack = newArray;
    }

    public void clear() {
        int[] newArray = new int[top+1];
        stack = newArray;
    }
}

     

2. 链表实现栈(Stack backed by Linked List)

      基本思路:用链表实现栈,关键是要保证链表头部位于栈顶,这将为入栈、出栈和清空操作带来极大的便利。

      入栈操作中,从栈的角度讲,新数据放在旧数据之上,而从链表的角度讲,就是新数据添加在链表头之前,再将链表头指向新数据。我们不断将链表头移至新数据的位置,形象地说,链表头是随着链表的扩展而不断向上移动的。这样,只需返回链表头指向的数据,就可以实现出栈功能。

      在显示数据时,如果想正确反映栈的结构,我们知道应当“从新至旧”打印,对应到这里的链表,就是从链表头开始向后遍历,这样打印出来,自然就是“自上而下”遍历栈的顺序。所以,我们只需单链表即可实现上述功能。

class LinkedListStack {
    private LinkedListNode head = null;
    private int size = 0;

    public boolean isEmpty() {
        return size == 0;
    }

    public void push(int value) {
        LinkedListNode node = new LinkedListNode(value);
        node.setNext(head);
        head = node;
        size++;
    }

    public int pop() {
        if (isEmpty()) {
            throw new NoSuchElementException("The stack is empty now");
        }

        int value = head.getData();
        head = head.getNext();
        size--;
        return value;
    }

    public void print() {
        if (isEmpty())
            throw new NoSuchElementException("The stack is empty now");

        LinkedListNode curr = head;
        int index = size - 1;
        while (curr != null) {
            System.out.printf("stack[%d] = %d\n", index--, curr.getData());
            curr = curr.getNext();
        }
    }

    public void clear() {
        head = null;
        size = 0;
    }
}

class LinkedListNode {
    private int data;
    private LinkedListNode next;

    public LinkedListNode(int data) {
        this.data = data;
    }

    public int getData() {
        return data;
    }

    public LinkedListNode getNext() {
        return next;
    }

    public void setNext(LinkedListNode next) {
        this.next = next;
    }
}

时间复杂度(Time Complexity)

      数组实现栈:入栈和出栈都作用在数组的尾部,不涉及其他元素的平移,因此均为O(1),但是入栈偶尔涉及数组的扩容(O(n)),因此准确来说是O(1)*。

      链表实现栈:入栈和出栈都作用在链表的头部,因此均为O(1)。

      数组和链表,对clear()方法的实现方式不同。

      对于数组来说,要想实现clear()方法至少有三种方式:(1)第一种是通过遍历的手段,将所有元素逐个设置为零或null,但这样做的时间复杂度为O(n),耗时太高。(2)第二种是不对数组做任何处理,在加入新的数据时,直接将其覆盖在原数据上。这种方式固然简单,但实际上原数据仍保留在数组中,用户仍可以获取到,这在一些实际问题中,可能是开发者不希望看到的。(3)第三种方式是创建一个同样大小的新数组,并将其赋给原数组。由于数组元素默认为零,那么清空后的数组所有元素均为零,而原数组则被垃圾处理机制回收了。本文上述代码即采用了这一种方式。

      而清空链表就简单得多,只需要把head设置为null,原有的数据就自动被清理掉了。

      这样,无论是用数组,还使用链表来实现栈,除显示所有数据的print()方法必须遍历所有数据之外,其余方法的时间复杂度均为O(1)。

注:本文章截图来自【尚硅谷】“数据结构与算法”视频,由韩顺平老师讲授。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值