1. 程式人生 > >Java陣列--自定義迴圈佇列(三)

Java陣列--自定義迴圈佇列(三)

自定義迴圈佇列

思考:

上篇文章中在討論佇列的出隊操作時候,其時間複雜度分析為O(n),這是因為每次出隊操作會刪除佇列的對頭,從而導致其陣列後續的元素都會往前移動n-1次,那怎麼樣才能實現隊列出隊操作的時候其時間複雜度為O(1)呢?

迴圈佇列定義
  • 佇列(queue)是隻允許在一端進行插入操作,而在另一端進行刪除操作的線性表。
  • 允許插入的一端稱為隊尾(Tail),允許刪除的一端稱為隊頭(Front),不允許在中間部位進行操作。
  • 佇列具有先進先出(FIFO),後進後出(LILO)的特性。
  • 其front和tail的索引座標能夠針對佇列容量進行迴圈變更,並且永遠有一個索引不儲存元素。

示意圖:

程式碼:

Queue泛型介面類:

/**
 * 定義一個泛型的佇列介面
 * @param <E>
 */
public interface Queue<E> {

    //入佇列,將一個元素新增到對尾
    void enqueue(E e);

    //出對列,將一個元素從對頭刪除,並且返回這個元素
    E dequeue();

    //獲取佇列對頭的元素
    E getFront();

    //判斷這個棧是否為空
    boolean isEmpty();

    //獲取棧中的元素個數
    int getSize();

}

LoopQueue泛型實現類:

/**
 * 基於陣列實現的迴圈佇列
 *
 * @param <E>
 */
public class LoopQueue<E> implements Queue<E> {

    //用來儲存佇列元素的陣列
    private E[] data;
    //佇列對頭的索引座標
    private int front;
    //佇列對尾的索引座標
    private int tail;
    //佇列中元素個數
    private int size;

    /**
     * 無參構造方法
     */
    public LoopQueue() {
        this(10);
    }

    /**
     * 有參構造方法
     *
     * @param capacity 佇列的容量
     */
    public LoopQueue(int capacity) {
        data = (E[]) new Object[capacity];
    }

    /**
     * 獲取佇列的容量
     *
     * @return 返回佇列的容量
     */
    public int getCapacity() {
        return data.length - 1;
    }

    /**
     * 判斷佇列元素是否為空
     *
     * @return 返回true代表為空,false代表不為空
     */
    public boolean isEmpty() {
        return front == tail;
    }

    /**
     * 獲取佇列中元素的個數
     *
     * @return 返回佇列中元素的個數
     */
    public int getSize() {
        return size;
    }

    /**
     * 向佇列中新增元素
     *
     * @param e 待新增的元素
     */
    public void enqueue(E e) {
        if ((tail + 1) % data.length == front) {
            int newCapacity = data.length + (data.length >> 1);
            resize(newCapacity);
        }
        data[tail] = e;
        tail = (tail + 1) % data.length;
        size++;
    }

    /**
     * 刪除佇列隊頭元素
     *
     * @return 返回佇列隊頭元素
     */
    @Override
    public E dequeue() {
        if (isEmpty())
            throw new IllegalArgumentException("Cannot dequeue from an empty queue.");

        E ret = data[front];
        data[front] = null;
        front = (front + 1) % data.length;
        size--;

        return ret;
    }

    /**
     * 獲取對列對頭的元素
     *
     * @return 返回佇列隊頭元素
     */
    @Override
    public E getFront() {
        if (isEmpty())
            throw new IllegalArgumentException("Cannot dequeue from an empty queue.");
        return data[front];
    }

    /**
     * 迴圈佇列私有的擴容操作 每次擴容1.5倍
     *
     * @param newCapacity 將要擴容的容量
     */
    private void resize(int newCapacity) {
        E[] newData = (E[]) new Object[newCapacity + 1];
        for (int i = 0; i < size; i++) {
            newData[i] = data[(i + front) % data.length];
        }
        data = newData;
        front = 0;
        tail = size;
        newData = null;
    }

    @Override
    public String toString() {
        StringBuffer res = new StringBuffer();
        res.append("Queue:front [");
        for (int i = front; i != tail; i = (i + 1) % data.length) {
            res.append(data[i]);
            if ((i + 1) % data.length != tail)
                res.append(", ");
        }
        res.append("] tail");
        res.append(String.format(" , size = %d , capacity = %d", size, data.length));
        return res.toString();
    }
}

陣列佇列的時間複雜度分析:

  • void enqueue(E e); ----O(1) 均攤,這裡的均攤是指可能會觸發陣列的擴容操作。
  • E dequeue();----O(1)。
  • E getFront();----O(1) 。
  • boolean isEmpty();----O(1) 。
  • int getSize();----O(1) 。