動手實現基於數組的棧和隊列

基礎知識

棧(stack)又名堆棧,它是一種運算受限的線性表。其限制是僅允許在表的一端進行插入和刪除運算。

棧是一種後進先出(LIFO)的數據結構。

隊列

隊列是一種特殊的線性表,特殊之處在於它只允許在表的前端(front)進行刪除操作,而在表的後端(rear)進行插入操作,和棧一樣,隊列是一種操作受限制的線性表。進行插入操作的端稱爲隊尾,進行刪除操作的端稱爲隊頭。

隊列是一種先進先出(FIFO)的數據結構。

制定手寫方案

如果我們要手寫一個棧或者隊列,先要確定用什麼數據結構保存數據,需要實現哪些功能?

選取數據結構

爲了更好的理解棧和隊列的原理,儘量採用簡單的數據結構來保存數據,這樣實現的邏輯會變得簡單易懂,所以本次採用的是數組。

實現哪些功能

對於棧:

  • 入棧和出棧
  • 獲取棧頂元素但不刪除此元素
  • 獲取棧是否爲空的方法
  • 獲取棧大小的方法

對於隊列:

  • 進隊列和出隊列
  • 獲取隊列頭元素但不刪除此元素
  • 獲取隊列是否爲空的方法
  • 獲取隊列大小的方法

使用數組實現棧:

import java.util.Arrays;
import java.util.EmptyStackException;


class Stack<E> {
    private E[] _data; // 數據
    private int _size; // 數組長度
    private int _realLength; // 數組已用長度

    @SuppressWarnings("unchecked")
    public Stack(int initSize) {
        this._size = initSize;
        this._data = (E[]) new Object[initSize];
        this._realLength = 0;
    }

    /**
     * 默認數組長度爲20
     */
    public Stack() {
        this(20);
    }

    /**
     * 入棧
     *
     * @param element 添加的元素
     */
    public void push(E element) {
        if (size() >= _size) {
            grow();
        }
        _data[_realLength++] = element;
    }

    /**
     * 出棧
     *
     * @return 返回棧頂的元素
     */
    public E pop() {
        if (size() < 1) {
            throw new EmptyStackException();
        }
        E top = _data[_realLength - 1];
        _data[--_realLength] = null;
        return top;
    }

    /**
     * 獲取棧頂元素
     *
     * @return 返回的是棧頂元素
     */
    public E peek() {
        if (size() < 1) {
            throw new EmptyStackException();
        }
        return _data[_realLength - 1];
    }

    /**
     * 判斷棧是否爲空棧
     *
     * @return true代表空棧
     */
    public boolean isEmpty() {
        return _realLength == 0;
    }

    /**
     * 棧的大小
     *
     * @return 大小值
     */
    public int size() {
        return _realLength;
    }

    /**
     * 數組的擴容,擴容大小爲原先的2倍
     */
    private void grow() {
        // 擴容後size也要變化
        _size = _size * 2;
        _data = Arrays.copyOf(_data, _size);
    }

    public static void main(String[] args) {
        Stack<Integer> stack = new Stack<>(1);
        stack.push(0);
        stack.push(1);
        stack.push(2);
        stack.push(3);
        System.out.println("stack is empty: " + stack.isEmpty());
        System.out.println("stack size is: " + stack.size());
        System.out.println("stack pop is: " + stack.pop());
        System.out.println("stack top is: " + stack.peek());
        // stack is empty: false
        // stack size is: 4
        // stack pop is: 3
        // stack top is: 2
    }
}

需要注意的幾個點:

  • 數組的擴容,做法是每次擴容原先長度的2倍
  • 出棧後數組的變更,元素出棧後需要將之前的元素置空,便於 GC
  • 對數組越界問題的把握,只要涉及到獲取元素的操作,都需要對下標進行是否越界判斷,可以返回 null,也可以直接拋出異常,具體看個人

使用數組實現隊列:

import java.util.Arrays;
import java.util.NoSuchElementException;


/**
 * FIFO
 */
class Queue<E> {

    private E[] _data;
    private int _realLength;
    private int _size;

    @SuppressWarnings("unchecked")
    public Queue(int initSize) {
        this._data = (E[]) new Object[initSize];
        this._size = initSize;
        this._realLength = 0;
    }

    /**
     * 默認數組長度爲20
     */
    public Queue() {
        this(20);
    }

    /**
     * 向隊列尾端添加元素
     *
     * @param element 元素
     */
    public void offer(E element) {
        if (size() >= _size) {
            grow();
        }
        _data[_realLength++] = element;
    }

    /**
     * 取出隊列頭元素,並且從隊列中刪除它
     *
     * @return 頭元素
     */
    public E poll() {
        if (isEmpty()) {
            throw new NoSuchElementException("queue is empty");
        }
        E first = _data[0];
        _data = Arrays.copyOfRange(_data, 1, _size);
        _realLength--;
        return first;
    }

    /**
     * 取出隊列頭元素,但是不刪除它
     *
     * @return 頭元素
     */
    public E element() {
        if (isEmpty()) {
            throw new NoSuchElementException("queue is empty");
        }
        return _data[0];
    }

    /**
     * 判斷隊列是否爲空
     *
     * @return true代表空隊列
     */
    public boolean isEmpty() {
        return _realLength == 0;
    }

    /**
     * 隊列的大小
     *
     * @return 大小值
     */
    public int size() {
        return _realLength;
    }

    /**
     * 數組的擴容,擴容大小爲原先的2倍
     */
    private void grow() {
        // 擴容後size也要變化
        _size = _size * 2;
        _data = Arrays.copyOf(_data, _size);
    }

    public static void main(String[] args) {
        Queue<Integer> queue = new Queue<Integer>();
        queue.offer(0);
        queue.offer(1);
        queue.offer(2);
        queue.offer(3);
        System.out.println("queue is empty: " + queue.isEmpty());
        System.out.println("queue size is: " + queue.size());
        System.out.println("queue poll: " + queue.poll());
        System.out.println("queue poll: " + queue.poll());
        System.out.println("queue poll: " + queue.poll());
        System.out.println("queue poll: " + queue.poll());
        // queue is empty: false
        // queue size is: 4
        // queue poll: 0
        // queue poll: 1
        // queue poll: 2
        // queue poll: 3
    }
}

使用數組來實現隊列相比較棧更加的容易,添加元素、判空和 size() 兩者都相似,只是在出隊列的時候做法與出棧不相同而已,出隊列出的是頭部元素,也就是最先添加的元素,出隊列之後需要重新調整數組,使用系統提供的 Arrays.copyOfRange(_data, 1, _size) 可輕鬆的複製數組一段元素。

手寫棧和隊列不僅可以用數組來實現,也可以用鏈表來實現,用鏈表可以可以更好的控制添加和刪除操作,畢竟鏈表採用的是指針方案。有興趣的可以自行實現一波。

如果本文章你發現有不正確或者不足之處,歡迎你在下方留言或者掃描下方的二維碼留言也可!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章