前言
看過筆者前兩篇介紹的Java版
資料結構陣列
和棧
的盆友,都給予了筆者一致的好評,在這裡筆者感謝大家的認可!!!
由於本章介紹的資料結構是佇列
,在佇列的實現上會基於前面寫的動態陣列
來實現,而佇列
又和棧
不論是從特點上和操作上都有類似之處,所以在這裡對這兩種資料結構不瞭解的朋友,可以去看一下筆者前兩篇文章介紹的資料結構陣列
和棧
,這裡筆者把連結貼出來(看過的盆友可以跳過此步驟...)
介紹
佇列是一種特殊的線性表,它只允許在表的前端(front)進行刪除操作,而在表的後端(rear)進行插入操作,和棧一樣,佇列是一種操作受限制的線性表。進行插入操作的端稱為隊尾,進行刪除操作的端稱為隊頭。
佇列的操作方式和棧
類似,唯一的區別在於佇列只允許新資料在後端(rear)進行新增。
特點
- 佇列是一種線性結構
- 只能從一端(隊尾)新增元素,從另一端(隊首)取出元素
- 先進先出,First In First Out(FIFO)
之前在介紹棧的時候,通過示意圖來幫助大家瞭解什麼是棧;這裡,我仍採用示意圖形式向大家演示佇列常用的兩個操作:入隊操作
和出隊操作
。
佇列入隊操作
這裡我們可以形象地想成我們到銀行辦理業務排隊的場景,現在A、B、C三個元素分別到銀行櫃檯排成一條隊辦理業務(我們都是文明的孩紙,總不能插隊O(∩_∩)O哈!),依次排隊的元素是:A、B、C。
佇列出隊操作
當元素A
辦理完業務時,當前是元素A
先離開佇列,然後是元素B
,最後是元素C
我們時刻要牢記佇列,入隊是從
隊尾
一端進行入隊,出隊是從隊首
一端進行出隊,是一種:先進先出的資料結構。
本文會介紹佇列的兩張實現方式,一種是陣列佇列,另外一種是迴圈佇列,考慮篇幅長度原因,本篇我們暫時只介紹陣列佇列,迴圈佇列放在下一篇介紹。
陣列佇列(底層基於陣列實現)
底層原理分析
現在我們宣告一個陣列的長度(capacity=3),元素個數為(size=0)的int型別陣列的空佇列,在這裡,假設對佇列的隊首
為陣列的左側
,隊尾
為陣列的右側
,示意圖如下:
現在如果我們有四個元素:A、B、C、D要入隊
元素A
入隊
元素A
已經入隊了,現在開始元素B
入隊
元素A
和元素B
已經入隊了,現在開始元素C
入隊
元素A
、B
和C
已經分別入隊了,現在如果我們要開始元素D
入隊,根據我們之前定義的動態陣列的特性,如果元素D
進行入隊操作,會發現此時我們的陣列已經滿了,這時候陣列會自動地擴容
(擴容的原理:新建一個容量是原陣列容量兩倍的陣列,把原陣列中的元素依次拷貝到新的陣列中,最後引用指向新的陣列)的原來的兩倍(具體擴容多少,盆友可以自行設定)示意圖如下:
到這裡我們已經完成了元素:A、B、C、D的入隊操作了,現在我們來看一下,它們的出隊操作,根據佇列的特性,佇列是一種先進先出
的資料結構,之前入隊操作順序依次是:A->B->C->D
,那麼出隊操作順序仍然是:A->B->C->D
現在我們來看一下元素A
和元素B
出隊後的示意圖:
元素C
和D
的出隊原理和元素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),第一時間獲取最新資訊!!!