重學資料結構(三、佇列)

三分惡發表於2020-08-27

@


1、佇列的定義和特點

和上一篇的棧相反,佇列(queue)是一種先進先出(First In First Out, FIFO)的線性表。

它只允許在表的一端進行插入,而在另一端刪除元素。這和日常生活中的排隊是一致的,最早進入佇列的元素最早離開。

在這裡插入圖片描述

在佇列中,允許插入的一端稱為隊尾(rear), 允許 刪除的一端則稱為隊頭(front)。出佇列和入佇列示意圖如下:

在這裡插入圖片描述


2、佇列的基本操作

佇列的基本運算和堆疊類似,包含判空、獲取長度、入隊、出隊、出隊、取隊頭(不刪除隊頭)等。

在這裡插入圖片描述
在這裡插入圖片描述

我們這裡定義一個佇列的介面。

/**
 * @Author 三分惡
 * @Date 2020/8/27
 * @Description 佇列
 */
public interface Queue {
    public boolean isEmpty();     //判空
    public int size();            //獲取佇列容量
    public void enQueue(Object element);   //入隊
    public Object deQueue();              //出隊
    public Object getHead();              //取隊首元素
}

佇列是一種受限的線性表,同樣有順序和鏈式兩種實現。


3、順序佇列

這裡順序佇列通過可擴容陣列來實現。

在類裡標記了隊頭和對尾的下標。

入隊時,隊尾往後移動,隊頭保持不變,出隊是隊頭往後移動,隊尾保持不變。

在這裡插入圖片描述
為什麼不保持隊頭指向0?因為如果隊首指向0,那麼出隊的時候需要將陣列前移,時間複雜度為O(n)。使用了隊頭和隊尾標記之後,出隊時隊頭往後移動一位,這樣避免了元素的移動。

/**
 * @Author 三分惡
 * @Date 2020/8/27
 * @Description 順序佇列
 */
public class ArrayQueue implements Queue{
    private static  int defaultSize=15;   //預設容量
    private int size;                    //實際容量:實際儲存元素個數
    private Object[] data;               //存放元素的陣列
    private int front=0;                   //隊頭(下標)
    private int rear=0;                    //隊尾(下標)

    /**
     * 無參構造方法:按預設容量構造元素陣列
     */
    public ArrayQueue(){
        data=new Object[defaultSize];
    }

    /**
     * 有參構造方法:指定元素陣列容量
     * @param capacity
     */
    public ArrayQueue(int capacity){
        data=new Object[capacity];
    }

    /**
     * 判空
     * @return
     */
    public boolean isEmpty() {
        return size==0;
    }

    /**
     * 獲取佇列元素個數
     * @return
     */
    public int size() {
        return size;
    }

    /**
     * 入隊
     * @param element
     */
    public void enQueue(Object element) {
        //如果隊滿
        if (size==data.length&&front==0){
            //真隊滿,擴容
            if (front==0){
                //擴容兩倍的新陣列
                Object [] newData=new Object[size<<1];
                //拷貝陣列
                System.arraycopy(data,0,newData,0,size);
                data=newData;
            }else{
                //假隊滿:前移元素
                //所有資料前移front位
                for (int i=front;i<size;i++){
                    data[i-front] = data[i];
                }
                //隊尾前移front位
                rear-=front;
                //隊頭指向0
                front=0;
            }
        }
        //隊尾插入元素
        data[rear]=element;
        rear++;
        size++;
    }

    /**
     * 出隊
     * @return
     */
    public Object deQueue() {
        if (isEmpty()){
            throw new RuntimeException("隊空");
        }
        //取隊頭元素
        Object f=data[front];
        //隊頭陣列元素指向null,幫助gc
        data[front]=null;
        //隊首指向下一元素
        front++;
        //元素個數減1
        size--;
        //返回隊首元素
        return f;
    }

    /**
     * 取隊首元素(不刪除隊首元素)
     * @return
     */
    public Object getHead() {
        if (isEmpty()){
            throw new RuntimeException("隊空");
        }
        return data[front];
    }
}

時間複雜度分析:

  • 入隊:平均O(1),最壞情況(擴容)O(n)
  • 出隊:O(1)
  • 取隊首:O(1)

3、鏈式佇列

這裡使用單向連結串列來實現鏈式佇列。

在這裡插入圖片描述
入隊是將隊尾指向插入的新元素,出隊是將隊頭指向隊頭的下一個元素。

/**
 * @Author 三分惡
 * @Date 2020/8/27
 * @Description
 */
public class LinkedQueue implements Queue{

    /**
     * 節點類
     * @param <T>
     */
    class Node<T>{
        private Object data;    //資料
        private Node next;    //下一節點
        Node(Object it, Node nextVal){
            this.data=it;
            this.next=nextVal;
        }
    }

    private Node front;       //隊頭
    private Node rear;        //隊尾
    private int size;         //佇列元素個數


    /**
     * 判空
     * @return
     */
    public boolean isEmpty() {
        return size==0;
    }

    public int size() {
        return size;
    }

    /**
     * 入棧
     * @param element
     */
    public void enQueue(Object element) {
        Node node=new Node(element,null);
        //如果佇列為空
        if (rear==null){
            rear=node;
            front=node;
        }else{
            rear.next=node;
        }
        size++;
    }

    /**
     * 出隊
     * @return
     */
    public Object deQueue() {
        //佇列為空
        if (front==null){
            throw new RuntimeException("佇列為空");
        }
        //隊頭
        Node node=front;
        front=front.next;
        size--;
        return node.data;
    }

    /**
     * 取隊頭元素
     */
    public Object getHead() {
        //佇列為空
        if (front==null){
            throw new RuntimeException("佇列為空");
        }
        return front.data;
    }
    
}

時間複雜度分析:

  • 入隊:O(1)
  • 出隊:O(1)
  • 取隊首:O(1)

除此之外,順序佇列有變種迴圈佇列,當rear到達陣列的最大下標時,重新指回陣列下標為0的位置;
鏈式佇列有雙端佇列,隊頭、隊尾都可以進行入隊、出隊操作的佇列,可以通過雙向連結串列實現;


4、java中的佇列

java中有一個佇列介面java.util.Queue,定義了佇列的一些方法。

它有一個子介面,java.util.Deque,定義了雙端佇列的方法。

LinkedList實現了java.util.Deque介面,所以LinkedList能作為佇列也能作為雙端佇列使用。詳見LinkedList原始碼閱讀筆記

在這裡插入圖片描述



原始碼地址:https://gitee.com/LaughterYoung/data-structure-learn.git


上一篇:重學資料結構(二、棧)




本文為學習筆記類部落格,主要資料來源如下!



參考:

【1】:鄧俊輝 編著. 《資料結構與演算法》
【2】:王世民 等編著 . 《資料結構與演算法分析》
【3】: Michael T. Goodrich 等編著.《Data-Structures-and-Algorithms-in-Java-6th-Edition》
【4】:嚴蔚敏、吳偉民 編著 . 《資料結構》
【5】:程傑 編著 . 《大話資料結構》
【6】:看完這篇你還不知道這些佇列,我這些圖白作了

相關文章