1. 程式人生 > 實用技巧 >佇列及其實現

佇列及其實現

什麼是佇列

具有一定操作約束的線性表。插入和刪除操作,只能在一端插入,另一端刪除。

資料插入稱之為入隊(addQ),資料刪除稱之為出隊(deleteQ),佇列最重要的特徵就是先進先出(FIFO)。生活中有很多跟佇列相關的例子,例如超市排隊。

佇列的抽象資料型別描述

與佇列相關的操作主要包括以下幾種:

  • 建立佇列:生成長度為 size 的空佇列。
  • 判斷佇列是否滿了。
  • 判斷佇列是否為空。
  • 將資料元素插入到佇列中。
  • 將資料元素從佇列中刪除。

佇列的順序儲存實現

佇列的順序儲存結構通常由一個一維陣列和一個記錄佇列頭元素位置的變數front以及一個記錄佇列尾元素位置的變數rear組成。

public class SeqQueue<T> implements Queue<T> {

    private T elementData[];

    private int front, rear;
}

如下圖所示,用順序儲存實現佇列,由於陣列的元素從 0 開始,所以 front 和 rear 同時指向 -1 這個位置,新增 Job1,rear 往後移動一個位置,刪除 Job1 ,front 往後移動一個位置。當佇列滿了的時候,就無法新增元素了,但是很明顯就能發現,此時之前刪除的位置還是空的,佇列中還有位置,只是無法新增而已,這樣的結構會造成空間浪費,我們需要用迴圈結構來解決。

迴圈佇列的機構如下圖所示。迴圈結構中,front 和 rear 開始時同時指向 0 這個位置,之後,每一次入隊,rear 向著順時針方向移動一個位置,每一次出佇列,front 向順時針方向移動一個位置。那麼這裡就有個問題:佇列空和滿的判別條件是什麼?佇列空和滿的時候,front=rear

,那麼就造成無法判斷佇列空還是滿了。那麼要如何解決呢?這裡提供兩個解決方法:

  1. 使用額外標記: Size或者tag 。size 用來記錄當前元素的個數,當你加入一個元素的時候,size 加 1,刪除一個元素的時候,size減1,所以只要根據size是0還是n就知道是空還是滿的。tag (0,1)標記,新增一個元素,tag=1,刪除一個元素 tag=0,當我們想判斷佇列是滿還是空時,只要判斷 tag 的值就知道最後一次操作是新增還是刪除。
  2. 僅使用n-1個數組空間。

我們採用第二種方案,使用求餘函式來檢視列隊是否已滿。

(rear + 1) % size == front

看看具體的實現程式碼:

package leetcode.editor.datastructure.queue;

import java.io.Serializable;

public class SeqQueue<T> implements Queue<T>, Serializable {

    private final static int DEAFULT_SIZE = 10;

    private T elementData[];

    private int front, rear;

    private int size;

    public SeqQueue() {
        elementData = (T[]) new Object[DEAFULT_SIZE];
        front = 0;
        rear = 0;
    }

    public SeqQueue(int size) {
        this.size = size;
        elementData = (T[]) new Object[size];
        front = 0;
        rear = 0;
    }

    public void add(T data) {
        if ((rear + 1) % this.elementData.length == front) {
            System.out.println("佇列已滿");
            return;
        }
        rear = (rear + 1) % this.elementData.length;
        elementData[rear] = data;
    }

    public boolean isEmpty() {
        return this.elementData.length == 0;
    }

    public T delete() {
        if (front == rear) {
            System.out.println("佇列為空");
            return null;
        } else {
            front = (front + 1) % this.elementData.length;
            return elementData[front];
        }
    }
}

佇列的鏈式儲存實現

佇列的鏈式儲存結構也可以用一個單鏈表實現。插入和刪除操作分別在連結串列的兩頭進行;佇列指標frontrear應該分別指向連結串列的表頭和表尾。

整個佇列的結構如下圖所示:

與順序結構不同的是,鏈式儲存實現的佇列,出隊需要在表頭進行,因為是單向連結串列,如果在表尾進行刪除操作,我們無法知道前一個元素是多少。因此入隊和出隊操作為:

public class LinkedQueue<T> implements Queue<T> {

    private Node<T> front; //指向隊頭節點

    private Node<T> rear; //指向隊尾節點

    public LinkedQueue() {
        this.front = null;
        this.rear = null;
    }

    @Override
    public void add(T data) {
        Node<T> node = new Node<>(data, null);
        if (this.front == null) {//空佇列插入
            this.front = node;
        } else {//非空佇列,尾部插入
            this.rear.next = node;
        }
        this.rear = node;
    }

    @Override
    public boolean isEmpty() {
        return front == null && rear == null;
    }

    @Override
    public T delete() {
        Node<T> frontCell;
        T frontElem;

        if (this.front == null) {
            System.out.println("佇列為空");
            return null;
        }
        frontCell = front;
        if (front == rear) //若佇列只有一個元素
            front = rear = null; //刪除後佇列置為空
        else
            front = front.next();//front移動到下一個元素
        frontElem = frontCell.data;
        return frontElem;
    }
}

public class Node<T> {

    public T data;
    public Node<T> next;

    public Node(T data) {
        this.data = data;
        next = null;
    }

    public Node(T data, Node<T> next) {
        this.data = data;
        this.next = next;
    }

    public Node<T> next() {
        return this.next;
    }

}

參考:浙江大學陳越老師的資料結構課程