1. 程式人生 > >一文詳解「佇列」,手擼佇列的3種方法!

一文詳解「佇列」,手擼佇列的3種方法!

> 本文已收錄至我的 Github《演算法圖解》系列:[https://github.com/vipstone/algorithm](https://github.com/vipstone/algorithm) 前面我們介紹了[棧(Stack)](https://mp.weixin.qq.com/s/HkDnPxuOAT3GmbMgMmIAgg),佇列和棧是比較像的一種資料結構。我們可以想象有很多輛汽車正在通過單行道的隧道,所有車輛不能插隊、不能掉頭,先進來的車也先出去,我們可以把這種特徵的資料結構稱之為佇列。 ![image.png](https://cdn.nlark.com/yuque/0/2020/png/92791/1602837043582-6d4b18ab-88a4-4647-9a0d-fffb492f0be1.png#align=left&display=inline&height=115&margin=%5Bobject%20Object%5D&name=image.png&originHeight=230&originWidth=900&size=20677&status=done&style=none&width=450) 佇列也屬於邏輯結構,所謂的物理結構是指可以將資料儲存在物理空間中,比如陣列和連結串列都屬於物理資料結構;而邏輯結構則是用於描述資料間的邏輯關係的,它可以由多種不同的物理結構來實現,比如佇列和棧都屬於邏輯結構。 ### 佇列特性 佇列中的元素必須是先進先出(First In First Out,FIFO)的,它有兩個重要的方法:入隊(enqueue)和出隊(dequeue)。佇列的入口端叫隊尾(rear),出口端叫隊頭(front),如下圖所示: ![image.png](https://cdn.nlark.com/yuque/0/2020/png/92791/1602929680047-21dffc83-18ae-43e7-bb69-3b6dfa7d34a0.png#align=left&display=inline&height=152&margin=%5Bobject%20Object%5D&name=image.png&originHeight=304&originWidth=972&size=16808&status=done&style=none&width=486) ### 手擼佇列 學習了佇列的基本知識之後,接下來我們將使用程式碼來實現一個佇列。 首先我們先使用陣列來實現一個佇列,它的結構如下圖所示: ![image.png](https://cdn.nlark.com/yuque/0/2020/png/92791/1602929519242-cd23c4e6-4763-47e7-bfb6-d8b751905963.png#align=left&display=inline&height=105&margin=%5Bobject%20Object%5D&name=image.png&originHeight=210&originWidth=982&size=12014&status=done&style=none&width=491) #### 1.自定義佇列—陣列 ```java public class MyQueue { private Object[] queue; // 儲存容器 private int head; // 頭部指標 private int tail; // 尾部指標 private int size; // 佇列實際儲存長度 private int maxSize; // 最大容量 public MyQueue() { // 無參建構函式,設定預設引數 this.maxSize = 10; this.head = 0; this.tail = -1; this.size = 0; this.queue = new Object[this.maxSize]; } public MyQueue(int initSize) { // 有參建構函式,設定引數 this.maxSize = initSize; this.head = 0; this.tail = -1; this.size = 0; this.queue = new Object[this.maxSize]; } /** * 查詢隊頭元素 */ public E peek() throws Exception { if (size == 0) { throw new Exception("佇列中暫無資料"); } return (E) this.queue[this.head]; } /** * 入列 */ public boolean offer(E e) throws Exception { if (tail >= (maxSize - 1)) { throw new Exception("新增失敗,佇列已滿"); } this.queue[++tail] = e; size++; return true; } /** * 出列 */ public E poll() throws Exception { if (size == 0) { throw new Exception("刪除失敗,佇列為空"); } size--; return (E) this.queue[head++]; } /** * 程式碼測試 */ public static void main(String[] args) throws Exception { MyQueue queue = new MyQueue(); queue.offer("Hello"); queue.offer("Java"); System.out.println(queue.peek()); queue.poll(); System.out.println(queue.poll()); } } ``` 以上程式碼的執行結果如下: > Hello > > Java #### 2.自定義佇列—連結串列 用連結串列實現佇列的資料結構如下圖所示: ![image.png](https://cdn.nlark.com/yuque/0/2020/png/92791/1602930045353-7650be48-ecff-4b36-b34d-e26e1c359666.png#align=left&display=inline&height=110&margin=%5Bobject%20Object%5D&name=image.png&originHeight=220&originWidth=1036&size=16468&status=done&style=none&width=518) 實現程式碼如下: ```java public class QueueByLinked { /** * 宣告連結串列節點 */ static class Node { E item; // 當前的值 Node next; // 下一個節點 Node(E e) { this.item = e; } } private Node firstNode; // 隊頭元素 private Node lastNode; // 隊尾元素 private int size; // 佇列實際儲存數量 private int maxSize; // 佇列最大容量 public QueueByLinked(int maxSize) { if (maxSize <= 0) throw new RuntimeException("佇列最大容量不能為空"); // 預設初始化函式 firstNode = lastNode = new Node(null); this.size = 0; this.maxSize = maxSize; } /** * 判斷佇列是否為空 */ public boolean isEmpty() { return size == 0; } /** * 入列 */ public void offer(Object e) { // 最大值效驗 if (maxSize <= size) throw new RuntimeException("佇列已滿"); Node node = new Node(e); lastNode = lastNode.next = node; // 設定最後一個節點和倒數第二個節點的 next size++; // 佇列數量 +1 } /** * 出列 */ public Node poll() { if (isEmpty()) throw new RuntimeException("佇列為空"); size--; // 佇列數量 -1 return firstNode = firstNode.next; // 設定並返回隊頭元素(第一個節點是 null,當前元素則為 Node.next) } /** * 查詢隊頭元素 */ public Node peek() { if (isEmpty()) throw new RuntimeException("佇列為空"); return firstNode.next; // 返回隊頭元素(第一個節點是 null,當前元素則為 Node.next) } /** * 程式碼測試 */ public static void main(String[] args) { QueueByLinked queue = new QueueByLinked(10); queue.offer("Hello"); queue.offer("JDK"); queue.offer("Java"); System.out.println(queue.poll().item); System.out.println(queue.poll().item); System.out.println(queue.poll().item); } } ``` 以上程式碼的執行結果如下: >
Hello > > JDK > > Java #### 3.擴充套件:使用 List 實現自定義佇列 除了以上兩種方式之外,我們還可以使用 Java 自身的資料結構來實現佇列,比如 List,我們這裡提供一個實現的思路(但並不建議在實際工作中使用),實現程式碼如下: ```java import java.util.ArrayList; import java.util.List; /** * 自定義佇列(List方式) */ public class QueueByList { private List value; // 佇列儲存容器 public QueueByList() { // 初始化 value = new ArrayList(); } /** * 判斷佇列是否為空 */ public boolean isEmpty() { return value.size() == 0; } /** * 入列 */ public void offer(Object e) { value.add(e); } /** * 出列 */ public E poll() { if (isEmpty()) throw new RuntimeException("佇列為空"); E item = (E) value.get(0); value.remove(0); return item; } /** * 查詢隊頭元素 */ public E peek() { if (isEmpty()) throw new RuntimeException("佇列為空"); return (E) value.get(0); } /** * 程式碼測試 */ public static void main(String[] args) { QueueByList queue = new QueueByList(); queue.offer("Hello"); queue.offer("JDK"); queue.offer("Java"); System.out.println(queue.poll()); System.out.println(queue.poll()); System.out.println(queue.poll()); } } ``` 以上程式碼的執行結果如下: > Hello > > JDK > > Java ### 佇列使用場景 佇列的常見使用場景有: - 儲存多執行緒中等待排隊執行的任務; - 儲存多執行緒公平鎖中等待執行任務的執行緒; - 常見訊息中介軟體的任務佇列等。 ### 總結 通過以上三種佇列的實現方式我們可以看出,任意容器都是可以用來實現佇列(Queue)的,只要保證佇列的元素先進先出(FIFO),並且在實現類中需要包含佇列的四個核心方法:入列、出列、查詢佇列是否為空、返回隊頭元素等,就可以稱為實現了一個自定義的佇列。 最後,給大家留一個問題:佇列的型別都有哪些?歡迎評論區留言,我會在下篇文章中給出答案。歡迎關注我,每天和你一起進步一