看得見的資料結構Android版之佇列篇

張風捷特烈發表於2018-11-30

零、前言

1.現實世界裡我們更多講究的是先來後到,先排隊先買票,這樣才有秩序,畢竟我們沒有計算機那麼有耐心
2.使用佇列結構能很好的模擬和解決類似生活中的事,比如訊息的傳送用佇列維護就是非常恰當的
3.佇列就像去動物園買票,先處理佇列的頭部,有新的人來了就後面排著去,慢慢等
4.還有一種很有意思的佇列是迴圈佇列,它是由於陣列對頭部操作的困難性,從而轉變一種思路,讓陣列也能很好的實現佇列結構,後面會仔細分析一下
5.本例操作演示原始碼:希望你可以和我在Github一同見證:DS4Android的誕生與成長,歡迎star

1.留圖鎮樓:佇列的最終實現的操作效果:
陣列實現普通佇列:

藍色區域是陣列看見:初始化四個空間,不夠再擴容,空閒太多再縮容

陣列實現普通佇列.gif

連結串列實現普通佇列:

佇列綜合操作.gif


2.佇列結構的簡介:
佇列是一種線性的資料結構  
特性:尾部新增,頭部取出 即先進先出FIFO  
操作:enqueue入隊  dequeue出隊  getFront檢視隊首元素
複製程式碼

佇列.png


一、佇列介面

兵馬未動,糧草先行,有介面好辦事。

/**
 * 作者:張風捷特烈
 * 時間:2018/8/17 0017:15:57
 * 郵箱:1981462002@qq.com
 * 說明:佇列介面
 */
public interface IQueue<T> {
    /**
     * 入隊
     * @param el 元素
     */
    void enqueue(T el);

    /**
     * 出隊
     * @return 元素
     */
    T dequeue();

    /**
     * 獲取隊首元素
     * @return 隊首元素
     */
    T getFront();

    /**
     * 獲取佇列元素個數
     * @return 元素個數
     */
    int getSize();

    /**
     * 是否為空
     * @return 是否為空
     */
    boolean isEmpty();
}
複製程式碼

二、普通佇列的陣列實現

普通佇列的陣列實現----效能非常差,後面用陣列實現迴圈佇列來優化
為什麼會很差,因為尾新增和頭刪除,總有一個會讓所有的人挪一挪,後面會用陣列實現迴圈佇列來優化

/**
 * 作者:張風捷特烈
 * 時間:2018/8/17 0017:15:57
 * 郵箱:1981462002@qq.com
 * 說明:普通佇列的陣列實現----效能非常差,後面用陣列實現迴圈佇列來優化
 */
public class ArrayChartQueue<E> implements IQueue<E> {
    private ArrayChart<E> array;//成員變數

    public ArrayChartQueue(int capacity) {
        this.array = new ArrayChart<>(capacity);
    }

    public ArrayChartQueue() {
        this.array = new ArrayChart<>();
    }

    @Override
    public void enqueue(E el) {
        array.add(el);
    }

    @Override
    public E dequeue() {
        return array.remove(0);
    }

    @Override
    public E front() {
        return array.get(0);
    }

    @Override
    public int size() {
        return array.size();
    }

    @Override
    public boolean isEmpty() {
        return array.isEmpty();
    }
}
複製程式碼
2.陣列普通佇列的插入演示:

由於是基於陣列來實現,所以一切的操作也是基於陣列
初始四個大小的陣列,就像招待處預留四把椅子,然後等椅子坐滿了,再來加椅子

陣列普通佇列的插入.gif

3.陣列普通佇列的檢視首元演示:

陣列普通佇列檢視首元.gif

4.陣列普通佇列的移除演示:

陣列表結構移除頭部...萬惡之源,千萬不要用,此處僅演示! 此處僅演示!此處僅演示!!
陣列表結構移除頭部...萬惡之源,千萬不要用,此處僅演示! 此處僅演示!此處僅演示!!

陣列普通佇列移除首元.gif


三、迴圈佇列

基於陣列實現的佇列在隊首取出時會使得整隊移動,效率會很低
但是壯哉我大陣列,豈會連個小小的佇列都搞不定,以後還哪還有臉立足王座...於是迴圈佇列出現了
說起迴圈大家腦子裡都是一個圈來回轉,迴圈小數表示不服... 只要有週期性就是迴圈,想成一個圈就狹隘了

1.迴圈佇列實現的思路:
不就是想要知道隊尾和隊首是那個嘛,我標出來,維護一下給你不就行了嗎  
注意:這裡的優勢在於維護了隊尾和隊首的標示,插入尾和刪除頭都是定點,而且陣列整體不移動,而是標示在動
新加元素時,隊尾表識後移,不夠就擴容。刪除頭時隊首標示  

迴圈佇列特點:
為空時,`隊尾標示==隊首標示`,
佇列滿:`(隊尾標示+1)%陣列長度==隊首標示`   
迴圈佇列會使隊首前一個位置不可用。
複製程式碼

迴圈佇列.png

迴圈佇列迴圈機制.png

/**
 * 作者:張風捷特烈
 * 時間:2018/8/17 0017:16:03
 * 郵箱:1981462002@qq.com
 * 說明:陣列實現迴圈佇列
 */
public class ArrayLoopQueue<T> implements IQueue<T> {
    private T[] data;// 佇列資料
    private int head;//隊首標示
    private int tail;//隊尾標示
    private int size;//元素個數
    public ArrayLoopQueue() {//無參構造:預設8個容量
        this(8);
    }
    public ArrayLoopQueue(int capacity) {
        // 因為會有一個浪費,所以+1
        data = (T[]) new Object[capacity + 1];
        head = 0;
        tail = 0;
        size = 0;
    }
    @Override
    public void enqueue(T el) {
        if (isFull()) {//加入時滿了---擴容
            grow(capacity() * 2);
        }
        data[tail] = el;//在隊尾插入
        //插入資料時對尾標示進行維護-----
        tail = (tail + 1) % data.length;
        size++;
    }
    @Override
    public T dequeue() {
        if (isEmpty()) {
            throw new IllegalArgumentException("MakeSure it's not an empty
        }
        T ret = data[head];
        data[head] = null;//讓隊首移除
        //隊首移除時對首標示進行維護-----
        head = (head + 1) % data.length;
        size--;
        //閒置太多---縮容
        if (size == capacity() / 4 && capacity() / 2 != 0 && size > 4) {
            grow(capacity() / 2);
        }
        return ret;
    }
    @Override
    public T front() {
        if (isEmpty()) {
            throw new IllegalArgumentException("MakeSure it's not an empty
        }
        return data[head];
    }
    /**
     * 擴容/縮容
     *
     * @param newCapacity 新的容量
     */
    private void grow(int newCapacity) {
        T[] newData = (T[]) new Object[newCapacity + 1];
        for (int i = 0; i < size; i++) {
            // 此時在newData中隊首對齊回來,data中就得有一個front的偏移量
            newData[i] = data[(i + head) % data.length];
        }
        data = newData;
        head = 0;
        tail = size;
    }
    /**
     * 獲取容量
     *
     * @return 容量
     */
    public int capacity() {
        return data.length - 1;
    }
    /**
     * 佇列元素個數
     *
     * @return 元素個數
     */
    @Override
    public int size() {
        return size;
    }
    /**
     * 是否為空
     *
     * @return 是否為空
     */
    @Override
    public boolean isEmpty() {
        return head == tail;
    }
    /**
     * 佇列是否滿了
     *
     * @return 佇列是否滿了
     */
    private boolean isFull() {
        // tail的下一個位置等於head時
        return (tail + 1) % data.length == head;
    }
}
複製程式碼

四、單連結串列式實現佇列結構

連結串列和佇列可謂天然配,連結串列的頭刪除,頭獲取很快,但尾新增要獲取尾部,需要遍歷一次,不好
但可以維護首位標識,使隊尾也容易獲取。(當然你也可以用雙連結串列...直接批件衣服,改都不用改)
註釋的很清楚了,看著程式碼順一下,或debug走一波,我就不贅述了

/**
 * 作者:張風捷特烈
 * 時間:2018/8/17 0017:22:50
 * 郵箱:1981462002@qq.com
 * 說明:單連結串列實現佇列
 */
public class SingleLinkedQueue<T> implements IQueue<T> {

    private Node head;//頭節點
    private Node tail;//尾節點

    private int size;//元素個數

    public SingleLinkedQueue() {
        head = null;
        tail = null;
        size = 0;
    }

    @Override
    public void enqueue(T el) {//入隊
        // 如果隊尾為空,說明佇列是空的。因為tail一直指向最後一個非空節點。
        if (tail == null) {
            tail = new Node(null, el);//初始化
            head = tail;
        } else {
            tail.next = new Node(null, el); // 新來的排到後面去
            tail = tail.next; //更新隊尾
        }
        size++;
    }

    @Override
    public T dequeue() {//出隊
        if (isEmpty())
            throw new IllegalArgumentException("MakeSure it's not an empty queue");
        Node targetNode = head;//我是老大
        head = head.next; // 我是老二,但我要篡位了...以後哥就是老大
        targetNode.next = null; //前任老大走了....
        if (head == null) {// 如果頭結點為空
            tail = null;
        }
        size--;
        return targetNode.el;
    }

    @Override
    public T front() {
        if (isEmpty())
            throw new IllegalArgumentException("MakeSure it's not an empty queue");
        return head.el;
    }

    @Override
    public int size() {
        return size;
    }

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

    private class Node {
        private T el;//改節點上的元素
        private Node next; //下一節點
        /**
         * 兩參構造
         *
         * @param next //下一節點
         * @param el   生成節點的元素值
         */
        private Node(Node next, T el) {
            this.el = el;
            this.next = next;
        }
    }
}
複製程式碼

五、小結

1.陣列普通佇列:ArrayChartQueue測試
方法\數量 1000 次10000次 10W次 100W次 1000次
enqueue 0.0006秒 0.0022秒 0.01571秒 0.06668秒 1.1375秒
dequeue 0.0111秒 0.2707秒 18.7684秒 ---- --
2.陣列環形佇列:ArrayLoopQueue測試
方法\數量 1000 次10000次 10W次 100W次 1000次
enqueue 0.0004秒 0.0019秒 0.01775秒 0.05414秒 0.6896秒
dequeu 0.0005秒 0.0021秒 0.0091秒 0.0360秒 0.3327秒
3.連結串列佇列:SingleLinkedStack測試
方法\數量 1000 次10000次 10W次 100W次 1000次
enqueue 0.0011秒 0.0031秒 0.0099秒 0.4881秒 3.1186秒
dequeue 0.0002秒 0.0013秒 0.0046秒 0.0221秒 0.1388秒

可見迴圈佇列還是蠻好的,壯哉,我大陣列!
陣列普通佇列,就認識一下吧...不要用它。
陣列環形佇列和連結串列佇列的比較也就相當於陣列和連結串列的比較


本系列後續更新連結合集:(動態更新)

後記:捷文規範

2.本文成長記錄及勘誤表
專案原始碼 日期 備註
V0.1--github 2018-11-24 看得見的資料結構Android版之佇列結構的實現
3.更多關於我
筆名 QQ 微信 愛好
張風捷特烈 1981462002 zdl1994328 語言
我的github 我的簡書 我的掘金 個人網站
4.宣告

1----本文由張風捷特烈原創,轉載請註明
2----歡迎廣大程式設計愛好者共同交流
3----個人能力有限,如有不正之處歡迎大家批評指證,必定虛心改正
4----看到這裡,我在此感謝你的喜歡與支援


icon_wx_200.png

相關文章