數據結構與算法 | 隊列的實現及其應用

Farming

原文連接:wangwei.one/posts/java-…html

前面,咱們學習了 棧的實現及應用 ,本篇咱們來學習一下最後一種線性表——隊列。java

隊列是咱們平常開發中常常會用到的一種數據結構,咱們常常使用隊列進行異步處理、系統解耦、數據同步、流量削峯、緩衝、限流等。例如,不是全部的業務都必須實時處理、不是全部的請求都必須實時反饋結果給用戶、不是全部的請求都必須100%處理成功、不知道誰依賴「我」的處理結果、不關心其餘系統如何處理後續業務、不須要強一致性,只需保證最終一致性便可、想要保證數據處理的有序性等等,這些問題都考慮使用隊列來解決。git

隊列

定義

隊列與 同樣,都是操做受限的線性表數據結構。隊列從一端插入數據,而後從另外一端取出數據。插入數據的一端稱爲"隊尾",取出數據的一端稱爲"隊頭",如圖所示:github

queue

特色

  • FIFO(First In First Out):先進先出原則

分類

同樣,隊列也分爲順序隊列鏈式隊列,分別使用數組與鏈表來實現。算法

鏈式隊列

鏈式隊列實現比較簡單,使用單鏈表便可實現,若是所示:數組

LinkedQueue

代碼實現

package one.wangwei.algorithms.datastructures.queue.impl;

import one.wangwei.algorithms.datastructures.queue.IQueue;

import java.util.NoSuchElementException;

/** * 鏈表隊列 * * @param <T> * @author https://wangwei.one * @date 2019/03/27 */
public class LinkedQueue<T> implements IQueue<T> {

    private int size = 0;
    private Node<T> head;
    private Node<T> tail;

    public LinkedQueue() {
    }

    /** * 添加元素到隊列頭部 * * @param value * @return */
    @Override
    public boolean offer(T value) {
        Node<T> last = tail;
        Node<T> newNode = new Node<>(value, null);
        tail = newNode;
        if (last == null) {
            head = newNode;
        } else {
            last.next = newNode;
        }
        size++;
        return true;
    }

    /** * 移除隊列尾部元素 * * @return */
    @Override
    public T poll() {
        if (head == null) {
            throw new NoSuchElementException("Queue underflow");
        }
        Node<T> tmpHead = head;
        head = head.next;
        tmpHead.next = null;
        size--;
        if (head == null) {
            tail = null;
        }
        return tmpHead.element;
    }

    /** * 查看隊列尾部元素值 * * @return */
    @Override
    public T peek() {
        if (head == null) {
            throw new NoSuchElementException("Queue underflow");
        }
        return head.element;
    }

    /** * 清除隊列元素 */
    @Override
    public void clear() {
        for (Node<T> x = head; x != null; ) {
            Node<T> next = x.next;
            x.element = null;
            x.next = null;
            x = next;
        }
        head = tail = null;
        size = 0;
    }

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

    /** * Node * * @param <T> */
    private static class Node<T> {
        private T element;
        private Node<T> next;

        private Node(T element) {
            this.element = element;
        }

        private Node(T element, Node<T> next) {
            this.element = element;
            this.next = next;
        }
    }
}

複製代碼

源碼數據結構

基於鏈表的實現方式,能夠實現一個支持無限排隊的無界隊列(unbounded queue),可是可能會致使過多的請求排隊等待,請求處理的響應時間過長。因此,針對響應時間比較敏感的系統,基於鏈表實現的無限排隊的線程池是不合適的。異步

順序隊列

順序隊列採用數組實現,數組的實現有兩種方式,一種是順序式的,一種是循環數組實現。ide

順序隊列

當隊列尾部沒有剩餘空間後,須要集中進行一次數據搬遷騰出空間,才能繼續進行入隊操做。如圖所示:post

ArrayQueue

循環隊列

順序隊列會存在數據搬遷的問題,對入隊操做有性能方面的影響。咱們能夠採用循環數組的方式來解決這一問題,如圖所示:

ArrayQueue

當隊尾無存儲空間且隊列未滿時,咱們能夠將其存儲到數組的前半部分剩餘的空間去。

代碼實現

循環隊列的實現關鍵在於隊列爲空和爲滿時的狀態判斷:

  • 當隊列爲空時:rear == front
  • 當隊列爲滿時:front == (rear + 1) % array.length,隊滿時,會浪費一個數組的存儲空間。

代碼以下:

package one.wangwei.algorithms.datastructures.queue.impl;

import one.wangwei.algorithms.datastructures.queue.IQueue;

import java.util.NoSuchElementException;

/** * 數組隊列 * * @param <T> * @author https://wangwei.one * @date 2019/02/04 */
public class ArrayQueue<T> implements IQueue<T> {

    /** * default array size */
    private static final int DEFAULT_SIZE = 1024;
    /** * 元素數組 */
    private T[] array;
    /** * 隊頭指針下標 */
    private int front = 0;
    /** * 隊尾指針下標 */
    private int rear = 0;

    public ArrayQueue() {
        this(DEFAULT_SIZE);
    }

    public ArrayQueue(int capacity) {
        array = (T[]) new Object[capacity];
    }

    /** * 添加隊尾元素 * * @param value * @return */
    @Override
    public boolean offer(T value) {
        if (isFull()) {
            grow();
        }
        array[rear % array.length] = value;
        rear++;
        return true;
    }

    /** * grow queue size doubly */
    private void grow() {
        int growSize = array.length << 1;
        T[] tmpArray = (T[]) new Object[growSize];
        int adjRear = rear % array.length;
        int endIndex = rear > array.length ? array.length : rear;
        if (adjRear < front) {
            System.arraycopy(array, 0, tmpArray, array.length - adjRear, adjRear + 1);
        }
        System.arraycopy(array, front, tmpArray, 0, endIndex - front);
        array = tmpArray;
        rear = (rear - front);
        front = 0;
    }

    /** * 移除隊頭元素 * * @return */
    @Override
    public T poll() {
        if (isEmpty()) {
            throw new NoSuchElementException("Queue underflow");
        }

        T element = array[front % array.length];
        array[front % array.length] = null;
        front++;
        if (isEmpty()) {
            // remove last element
            front = rear = 0;
        }

        int shrinkSize = array.length >> 1;
        if (shrinkSize >= DEFAULT_SIZE && size() < shrinkSize) {
            shrink();
        }
        return element;
    }

    /** * 壓縮 */
    private void shrink() {
        int shrinkSize = array.length >> 1;
        T[] tmpArray = (T[]) new Object[shrinkSize];
        int adjRear = rear % array.length;
        int endIndex = rear > array.length ? array.length : rear;
        if (adjRear <= front) {
            System.arraycopy(array, 0, tmpArray, array.length - front, adjRear);
        }
        System.arraycopy(array, front, tmpArray, 0, endIndex - front);
        array = null;
        array = tmpArray;
        rear = rear - front;
        front = 0;
    }

    /** * 查看隊頭元素 * * @return */
    @Override
    public T peek() {
        if (isEmpty()) {
            throw new NoSuchElementException("Queue underflow");
        }
        return array[front % array.length];
    }

    /** * 清除隊列元素 */
    @Override
    public void clear() {
        array = null;
        front = rear = 0;
    }

    /** * 隊列大小 */
    @Override
    public int size() {
        return rear - front;
    }

    /** * 判斷隊列是否滿 * * @return */
    private boolean isFull() {
        return !isEmpty() && (front == (rear + 1) % array.length);
    }

    /** * 判斷隊是否爲空 * * @return */
    private boolean isEmpty() {
        return size() <= 0;
    }

}

複製代碼

源碼

基於數組實現的有界隊列(bounded queue),隊列的大小有限,當請求數量超過隊列大小時,接下來的請求就會被拒絕,這種方式對響應時間敏感的系統來講,就相對更加合理。不過,設置一個合理的隊列大小,也是很是有講究的。隊列太大致使等待的請求太多,隊列過小會致使沒法充分利用系統資源、發揮最大性能。

參考資料

相關文章
相關標籤/搜索