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

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

本文已收錄至我的 Github《演算法圖解》系列:https://github.com/vipstone/algorithm

前面我們介紹了棧(Stack),佇列和棧是比較像的一種資料結構。我們可以想象有很多輛汽車正在通過單行道的隧道,所有車輛不能插隊、不能掉頭,先進來的車也先出去,我們可以把這種特徵的資料結構稱之為佇列。


佇列也屬於邏輯結構,所謂的物理結構是指可以將資料儲存在物理空間中,比如陣列和連結串列都屬於物理資料結構;而邏輯結構則是用於描述資料間的邏輯關係的,它可以由多種不同的物理結構來實現,比如佇列和棧都屬於邏輯結構。

佇列特性

佇列中的元素必須是先進先出(First In First Out,FIFO)的,它有兩個重要的方法:入隊(enqueue)和出隊(dequeue)。佇列的入口端叫隊尾(rear),出口端叫隊頭(front),如下圖所示:

手擼佇列

學習了佇列的基本知識之後,接下來我們將使用程式碼來實現一個佇列。

首先我們先使用陣列來實現一個佇列,它的結構如下圖所示:

1.自定義佇列—陣列

public class MyQueue<E> {

    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.自定義佇列—連結串列

用連結串列實現佇列的資料結構如下圖所示:

實現程式碼如下:

public class QueueByLinked {

    /**
     * 宣告連結串列節點
     */
    static class Node<E> {
        E item; // 當前的值

        Node<E> 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,我們這裡提供一個實現的思路(但並不建議在實際工作中使用),實現程式碼如下:

import java.util.ArrayList;
import java.util.List;

/**
 * 自定義佇列(List方式)
 */
public class QueueByList<E> {

    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),並且在實現類中需要包含佇列的四個核心方法:入列、出列、查詢佇列是否為空、返回隊頭元素等,就可以稱為實現了一個自定義的佇列。

最後,給大家留一個問題:佇列的型別都有哪些?歡迎評論區留言,我會在下篇文章中給出答案。歡迎關注我,每天和你一起進步一點點~