Java版-資料結構-佇列(陣列佇列)

小白程式之路發表於2019-03-14

前言

看過筆者前兩篇介紹的Java版資料結構陣列的盆友,都給予了筆者一致的好評,在這裡筆者感謝大家的認可!!!

由於本章介紹的資料結構是佇列,在佇列的實現上會基於前面寫的動態陣列來實現,而佇列又和不論是從特點上和操作上都有類似之處,所以在這裡對這兩種資料結構不瞭解的朋友,可以去看一下筆者前兩篇文章介紹的資料結構陣列,這裡筆者把連結貼出來(看過的盆友可以跳過此步驟...)

介紹

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

佇列的操作方式和類似,唯一的區別在於佇列只允許新資料在後端(rear)進行新增。

特點

  • 佇列是一種線性結構
  • 只能從一端(隊尾)新增元素,從另一端(隊首)取出元素
  • 先進先出,First In First Out(FIFO)

之前在介紹棧的時候,通過示意圖來幫助大家瞭解什麼是棧;這裡,我仍採用示意圖形式向大家演示佇列常用的兩個操作:入隊操作出隊操作

佇列入隊操作

image-20190314004824471

這裡我們可以形象地想成我們到銀行辦理業務排隊的場景,現在A、B、C三個元素分別到銀行櫃檯排成一條隊辦理業務(我們都是文明的孩紙,總不能插隊O(∩_∩)O哈!),依次排隊的元素是:A、B、C。

佇列出隊操作

image-20190314004945601

當元素A辦理完業務時,當前是元素A先離開佇列,然後是元素B,最後是元素C

我們時刻要牢記佇列,入隊是從隊尾一端進行入隊,出隊是從隊首一端進行出隊,是一種:先進先出的資料結構。

本文會介紹佇列的兩張實現方式,一種是陣列佇列,另外一種是迴圈佇列,考慮篇幅長度原因,本篇我們暫時只介紹陣列佇列,迴圈佇列放在下一篇介紹。

陣列佇列(底層基於陣列實現

底層原理分析

現在我們宣告一個陣列的長度(capacity=3),元素個數為(size=0)的int型別陣列的空佇列,在這裡,假設對佇列的隊首為陣列的左側隊尾為陣列的右側,示意圖如下:

image-20190314023306672

現在如果我們有四個元素:A、B、C、D要入隊

元素A入隊

image-20190314025039492

元素A已經入隊了,現在開始元素B入隊

image-20190314025109889

元素A和元素B已經入隊了,現在開始元素C入隊

image-20190314055125456

元素ABC已經分別入隊了,現在如果我們要開始元素D入隊,根據我們之前定義的動態陣列的特性,如果元素D進行入隊操作,會發現此時我們的陣列已經滿了,這時候陣列會自動地擴容(擴容的原理:新建一個容量是原陣列容量兩倍的陣列,把原陣列中的元素依次拷貝到新的陣列中,最後引用指向新的陣列)的原來的兩倍(具體擴容多少,盆友可以自行設定)示意圖如下:

image-20190314055438791

到這裡我們已經完成了元素:A、B、C、D的入隊操作了,現在我們來看一下,它們的出隊操作,根據佇列的特性,佇列是一種先進先出的資料結構,之前入隊操作順序依次是:A->B->C->D,那麼出隊操作順序仍然是:A->B->C->D

現在我們來看一下元素A和元素B出隊後的示意圖:

image-20190314060105701

元素CD的出隊原理和元素A出隊的原理一樣,直至全部出隊完成,變成空佇列

在元素出隊的過程中,相應地也會進行縮容操作,之前筆者這邊定義,當陣列中元素的個數(size)等於陣列容量(capacity)的一半時,陣列會進行縮容操作,這也正是動態陣列的特點。

瞭解了陣列佇列的底層原理之後,接下來我們用程式碼來實現一下(建議盆友,在看之前,自己可以嘗試寫一下,然後在看,這樣印象可能會比較深刻O(∩_∩)O哈!)

佇列基本操作
  • 向佇列中新增元素(入隊)
void enqueue(E e);
複製程式碼
  • 從佇列中取出元素(出隊)
E dequeue();
複製程式碼
  • 獲取隊首元素
E getFront();
複製程式碼
  • 獲取佇列中元素個數
int getSize();
複製程式碼
  • 判斷佇列是否為空
boolean isEmpty();
複製程式碼
程式碼實現

介面定義Queue

public interface Queue<E> {
    /**
     * 入隊
     *
     * @param e
     */
    void enqueue(E e);

    /**
     * 出隊
     *
     * @return
     */
    E dequeue();

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

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

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

DynamicArrayQueue 類實現介面 Queue

public class DynamicArrayQueue<E> implements Queue<E> {

    /**
     * 用陣列存放佇列中元素的個數
     */
    private DynamicArray<E> dynamicArray;

    /**
     * 指定容量,初始化佇列
     *
     * @param capacity
     */
    public DynamicArrayQueue(int capacity) {
        dynamicArray = new DynamicArray<>(capacity);
    }

    /**
     * 預設容量,初始化佇列
     */
    public DynamicArrayQueue() {
        dynamicArray = new DynamicArray<>();
    }


    @Override
    public void enqueue(E e) {
        dynamicArray.addLast(e);
    }

    @Override
    public E dequeue() {
        return dynamicArray.removeFirst();
    }

    @Override
    public E getFront() {
        return dynamicArray.getFirst();
    }

    @Override
    public int getSize() {
        return dynamicArray.getSize();
    }

    @Override
    public boolean isEmpty() {
        return dynamicArray.isEmpty();
    }

    @Override
    public String toString() {
        return "DynamicArrayQueue{" +
                "【隊首】dynamicArray=" + dynamicArray + "}【隊尾】";
    }
}
複製程式碼

測試類: DynamicArrayQueueTest

public class DynamicArrayQueueTest {
    @Test
    public void testArrayQueue() {
        // 指定容量(capacity=6)初始化佇列
        DynamicArrayQueue<String> dynamicArrayQueue = new DynamicArrayQueue(3);
        System.out.println("初始佇列:" + dynamicArrayQueue);

        // 準備入隊元素
        List<String> enQueueElements = Arrays.asList("A", "B", "C");

        // 元素入隊
        enQueueElements.forEach(x -> dynamicArrayQueue.enqueue(x));
        System.out.println("元素A、B、C入隊:" + dynamicArrayQueue);

        // 此時如果又有一個元素D入隊,會發生擴容操作 (size == capacity)進行擴容
        dynamicArrayQueue.enqueue("D");
        System.out.println("元素D入隊,發生擴容:" + dynamicArrayQueue);


        // 元素A出隊,會發生縮容操作(size == capacity / 2)進行縮容
        dynamicArrayQueue.dequeue();
        System.out.println("元素A出隊,發生縮容:" + dynamicArrayQueue);

        // 元素B出隊
        dynamicArrayQueue.dequeue();
        System.out.println("元素B出隊:" + dynamicArrayQueue);

    }
}
複製程式碼

執行結果

初始佇列:DynamicArrayQueue{【隊首】dynamicArray=DynamicArray{data=[null, null, null], size=0,capacity=3}}【隊尾】

元素A、B、C入隊:DynamicArrayQueue{【隊首】dynamicArray=DynamicArray{data=[A, B, C], size=3,capacity=3}}【隊尾】

元素D入隊,發生擴容:DynamicArrayQueue{【隊首】dynamicArray=DynamicArray{data=[A, B, C, D, null, null], size=4,capacity=6}}【隊尾】

元素A出隊,發生縮容:DynamicArrayQueue{【隊首】dynamicArray=DynamicArray{data=[B, C, D], size=3,capacity=3}}【隊尾】

元素B出隊:DynamicArrayQueue{【隊首】dynamicArray=DynamicArray{data=[C, D, null], size=2,capacity=3}}【隊尾】
複製程式碼

細心的盆友,會發現,因為佇列的底層是陣列來實現的,佇列的出隊操作實際上就是:刪除陣列中的第一個元素,後面的所有元素都要往前面挪一位;其實這樣效能是比較低下的,時間複雜度是O(n)級別的。

我們想如果元素進行出隊操作後,能否不挪動後面的元素,還能維持佇列的特性,這樣問題不就解決了嗎?盆友可以自行思考一下。

完整版程式碼GitHub倉庫地址:Java版資料結構-佇列(陣列佇列) 歡迎大家【關注】和【Star

本篇完成的陣列佇列是基於之前【Java版-資料結構-陣列】動態陣列來實現的,下一篇筆者會給大家介紹用迴圈佇列來解決陣列佇列帶來的效能問題。接下來,筆者還會一一的實現其它常見的陣列結構。

  • 靜態陣列
  • 動態陣列
  • 陣列佇列
  • 迴圈佇列
  • 連結串列
  • 迴圈連結串列
  • 二分搜尋樹
  • 優先佇列
  • 線段樹
  • 字典樹
  • AVL
  • 紅黑樹
  • 雜湊表
  • ....

持續更新中,歡迎大家關注公眾號:小白程式之路(whiteontheroad),第一時間獲取最新資訊!!!

Java版-資料結構-佇列(陣列佇列)

相關文章