【數據結構】隊列實現的5種方式及時間復雜度對比分析


1. 使用數組實現一個簡單的隊列

/**
 *           ===========================
 * 隊列首部   00000000000000000000000000   隊列尾部
 *           ===========================
 */
public class ArrayQueue<Element> implements Queue<Element>{

    // 通過內部的array來實現
    private Array<Element> array;
    // 構造函數
    public ArrayQueue(int capacity){
        this.array = new Array<>(capacity);
    }
    // 默認的構造函數
    public ArrayQueue(){
        this.array = new Array<>();
    }

    @Override
    public int getSize() {
        return this.array.getSize();
    }

    @Override
    public boolean isEmpty() {
        return this.array.isEmpty();
    }

    public int getCapacity(){
        return this.array.getCapacity();
    }

    @Override
    public void enqueue(Element element) {
        // 進入隊列(數組的末尾來添加元素)
        this.array.addLast(element);
    }

    @Override
    public Element dequeue() {
        // 出隊列(刪除最后一個元素),數組的第一個元素
        return this.array.removeFirst();
    }

    @Override
    public Element getFront() {
        // 獲取第一個元素(對首部的第一個元素)
        return this.array.getFirst();
    }


    @Override
    public String toString(){
        StringBuilder stringBuilder = new StringBuilder();
        // 使用自定義的方式實現數組的輸出
        stringBuilder.append("Queue:");
        // 開始實現數組元素的查詢
        stringBuilder.append("front [");
        for (int i = 0; i < this.array.getSize(); i++) {
            stringBuilder.append(this.array.get(i));
            // 開始實現數組元素的回顯(只要下表不是最后一個元素的話,就直接輸出這個元素)
            if (i != this.array.getSize()-1)
                stringBuilder.append(", ");
        }
        stringBuilder.append("] tail");
        // 實現數組元素的輸出
        return stringBuilder.toString();
    }
}

2. 使用數組實現一個循環隊列(維護一個size變量)

/**
 * 循環隊列的幾個要點:
 * 1. tail = head 說明隊列就是滿的
 * 2. 循環隊列需要空出來一個位置
 */
public class LoopQueue<E> implements Queue {
    private E[] data;
    private int head;               // 首部指針
    private int tail;               // 尾部指針
    private int size;           // 可以通過head以及tail的位置情況去計算size的大小【注意是不能直接使用getCapacity的】


    // 實現循環隊列
    public LoopQueue() {
        // 設置一個隊列的默認的大小
        this(10);
    }

    // 循環隊列的實現
    public LoopQueue(int capacity) {
        this.data = (E[]) new Object[capacity + 1];
        // 需要多出來一個
        this.head = 0;
        this.tail = 0;
        this.size = 0;
    }

    @Override
    public int getSize() {
        // 計算容量大小
        return this.size;
    }


    // capacity表示的這個隊列中最大可以容納的元素個數【這是一個固定值】
    @Override
    public int getCapacity() {
        // 由於隊列默認占了一個空的位置,因此sizt = this.length-1
        return this.data.length - 1;
    }


    // 當head和tail的值相同的時候隊列滿了
    public boolean isEmpty() {
        return head == tail;
    }


    @Override
    public void enqueue(Object value) {
        // 1. 開始判斷隊列有沒有充滿, 因為數組的下表是不會改變的,所以這里需要以數組的下標為一個參考點, 而不是this.data.length-14
        if ((tail + 1) % this.data.length == head) {
            // 2. 開始進行擴容, 原來的二倍, 由於默認的時候已經白白浪費了一個空間,因此這里就直接擴容為原來的二倍即可
            resize(2 * getCapacity());
        }

        // 2. 如果沒有滿的話,就開始添加元素
        this.data[tail] = (E) value;
        // 3. 添加完畢之后(tail是一個循環的位置,不可以直接相加)
//        this.tail = 2;
        this.tail = (this.tail+1) % data.length;  // data.length對於數組的下標
//        int i = (this.tail + 1) % data.length;
        // 4. 隊里的長度
        this.size++;
    }


    /**
     * 開始resize數組元素
     *
     * @param capacity
     */
    private void resize(int capacity) {
        // 開始進行數組的擴容
        // 1. 創建一個新的容器(默認多一個位置)
        E[] newData = (E[]) new Object[capacity + 1];
        // 2. 開始轉移元素到新的容器
        for (int i = 0; i < size; i++) {
            int index = (head + i) % data.length;
            newData[i] = data[index];
        }

        // 添加完畢之后,開始回收之前的data,data里面的數據一直是最新的數據
        this.data = newData;        // jvm的垃圾回收機制會自動回收內存data

        // 3. 添加完畢
        this.head = 0;
        // 4. 指向尾部
        this.tail = size;
    }


    @Override
    public Object dequeue() {
        // 1. 先來看下隊列中有沒有元素數據
        if (this.size == 0)
            throw new IllegalArgumentException("Empty queue cannot dequeue!");
        // 2. 開始看一下內存的大小
        Object ret = this.data[head];
        // 3. 開始刪除數據
        this.data[head] = null;



        // TODO 注意點:直接使用+1而不是使用++
        // 4. 開始向后移動, 為了防止出錯,盡量使用 this.head+1來進行操作,而不是使用 this.haad++, 否則可能會出現死循環的情況【特別注意!!!!!!!!!!!!】
        this.head = (this.head+1) %  data.length;

        // 5. 計算新的容量大小
        this.size--;


        // 6. 開始進行縮容操作, d當容量為1, size為0的時候,就不需要縮小容量、、
        if (this.size == this.getCapacity() / 4)
            // 縮小為原來的一半大小的容量
            resize(this.getCapacity() / 2);

        // 獲取出隊列的元素
        return ret;
    }


    @Override
    public Object getFront() {
        // 由於front的位置一直是隊列的第一個元素,直接返回即可
        if (this.size == 0)
            throw new IllegalArgumentException("Empty queue cannot get element!");
        return this.data[head];
    }



    @Override
    public String toString() {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(String.format("Array : size=%d, capacity=%d; ", size, getCapacity()));
        stringBuilder.append("LoopQueue head [");


        // 第一個元素在head的位置,最后一個元素在tail-1的位置
        // 注意循環隊列的遍歷方式,是一個循環
        for (int i = this.head; i != tail; i = (i+1) % data.length) {
            stringBuilder.append(data[i]);
            // 只要不是最后一個元素的話, 由於循環條件中已經規定了i!=tail, 因此這里是不能直接這樣來判斷的
            if ((i+1)%data.length == this.tail) {
                // 此時就是最后一個元素
            } else {
                stringBuilder.append(", ");
            }

        }


        stringBuilder.append("] tail");
        return stringBuilder.toString();
    }
}

3.使用數組實現一個循環隊列(不維護size變量)

/**
 * 使用數組實現一個循環隊列的數據結構
 */
public class WithoutSizeLoopQueue<E> implements Queue<E> {
    private E[] data;
    private int head;
    private int tail;

    public WithoutSizeLoopQueue(int capacity) {
        // 泛型不能直接被實例化, 由於循環隊列默認會空出來一個位置,這里需要注意
        this.data = (E[]) new Object[capacity + 1];
        this.head = 0;
        this.tail = 0;
    }

    public WithoutSizeLoopQueue() {
        this(10);
    }


    @Override
    public int getSize() {
        // 計算數組的元素個數
        if (tail >= head) {
            return tail - head;
        } else {
            int offSet = head - tail;
            return this.data.length - offSet;
        }
    }

    @Override
    public int getCapacity() {
        return data.length - 1;
    }

    @Override
    public void enqueue(E value) {
        // 開始進入隊列
        // 1. 先來看下隊列有沒有滿
        if ((tail + 1) % data.length == head)
            // 開始擴大容量
            resize(2 * getCapacity());
        // 2. 開始進入隊列
        data[tail] = value;
        // 3. 開始移動下標
        tail++;
        // 4. 開始更新容量【沒必要】
    }

    public void resize(int newCapacity) {
        // 1.  更新為新的容量
        E[] newData = (E[]) new Object[newCapacity + 1];
        // 2. 開始轉移數據到新的數組中去
        for (int i = 0; i < getSize(); i++) {
            newData[i] = data[(i + head) % data.length];
        }
        // 3. 銷毀原來的數據信息
        data = newData;


        // 【錯誤警告】bug:移動到新的這個數組里面之后,需要重新改變head和tail的位置【bug】
        tail = getSize();           // 尾部節點始終指向最后一個元素的后面一個位置,  此時如果直接使用getSize實際上獲取到的是上一次的元素的個數,而不是最新的元素的個數信息(需要放在前面,否在獲取到的size就不是同一個了,特別重要)
        head = 0;                   // 頭節點指向的是第一個元素


        // 4. 直接銷毀
        newData = null;
    }

    @Override
    public E dequeue() {
        // 1.先來看下隊列是否為空
        if (getSize() == 0)
            throw new IllegalArgumentException("Empty queue cannot dequeue!");
        // 2.開始出head位置的元素
        E value = data[head];
        // 3. 刪除元素
        data[head] = null;
        // 4. 移動head
        head = (head+1) % data.length;

        // 此時需要進行一次判斷
        if (getSize() == getCapacity() / 4 && getCapacity() / 2 != 0)
            resize(getCapacity() / 2);


        // 如果數組縮小容量之后,這里的

        return value;
    }

    @Override
    public E getFront() {
        return dequeue();
    }


    @Override
    public String toString(){
        // 開始遍歷輸出隊列的元素
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(String.format("LoopQueue: size = %d, capacity = %d\n", getSize(), getCapacity()));
        stringBuilder.append("front ");
        // 從head位置開始遍歷
        for (int i = head; i != tail ; i = (i+1) % data.length) {
            stringBuilder.append(data[i]);
            if ((i + 1) % data.length != tail)
                stringBuilder.append(", ");
        }
        return stringBuilder.append(" tail").toString();
    }

}

4. 使用鏈表實現一個隊列

/**
 * 使用鏈表實現的隊列
 */
public class LinkedListQueue<E> implements Queue<E>{

    // 這是一個內部類,只能在這個類的內部可以訪問(用戶不需要了解底層是如何實現的)
    private class Node {
        // 用於存儲元素
        public E e;
        // 用於存儲下一個節點
        public Node next;

        public Node(E e, Node next) {
            this.e = e;
            this.next = next;
        }

        public Node(E e) {
            this(e, null);
        }

        public Node() {
            this(null, null);
        }

        @Override
        public String toString() {
            return e.toString();
        }
    }


    // 定義隊列需要的參數
    private Node head, tail;
    private int size;

    public LinkedListQueue(){
        this.head = null;
        this.tail = null;
        this.size = 0;
    }



    @Override
    public int getSize() {
        return this.size;
    }

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


    @Override
    public int getCapacity() {
        return 0;
    }


    /**
     *  入隊
     * @param value
     */
    @Override
    public void enqueue(E value) {
        // 入隊從尾部開始
        if (tail == null){
            // 1. 如果此時沒有元素的話
            tail = new Node(value);
            head = tail;
        }
        else {
             // 2. 如果已經存在了元素,那么隊列尾部肯定是不為空的,而是指向了一個空節點
            tail.next = new Node(value);
            // 移動尾節點
            tail = tail.next;
        }
        size++;
    }


    /**
     * 出隊
     * @return
     */
    @Override
    public E dequeue() {
        if (isEmpty())
            throw new IllegalArgumentException("Empty queue cannot dequeue!");
        // 1. 存儲出隊的元素
        Node retNode = head;
        // 2.head節點下移動
        head = head.next;
        // 3.刪除原來的頭節點
        retNode.next = null;        // 實際上刪除的是這個節點的數據域


        // 如果刪除了最后一個元素之后
        if (head == null)
            tail = null;

        size--;
        return retNode.e;
    }

    @Override
    public E getFront() {
        if (isEmpty())
            throw new IllegalArgumentException("Empty queue cannot dequeue!");
        return head.e;
    }



    @Override
    public String toString(){
        StringBuilder stringBuilder = new StringBuilder();
        Node cur = head;
        stringBuilder.append("head:");
        //  1.  第一種循環遍歷的方式,  注意這里判斷的是每一次的cur是否為空, 而不是判斷cur.next, 否則第一個元素是不能打印輸出的
        while (cur != null) {
            stringBuilder.append(cur + "->");
            // 繼續向后移動節點
            cur = cur.next;
        }
        stringBuilder.append("NULL tail");
        return stringBuilder.toString();
    }
}

5. 使用一個帶有虛擬頭結點的鏈表實現一個隊列

/**
 * 帶有虛擬頭結點的鏈表實現的隊列
 */
public class DummyHeadLinkedListQueue<E> implements Queue<E> {
    // 這是一個內部類,只能在這個類的內部可以訪問(用戶不需要了解底層是如何實現的)
    private class Node {
        // 用於存儲元素
        public E e;
        // 用於存儲下一個節點
        public Node next;

        public Node(E e, Node next) {
            this.e = e;
            this.next = next;
        }

        public Node(E e) {
            this(e, null);
        }

        public Node() {
            this(null, null);
        }

        @Override
        public String toString() {
            return e.toString();
        }
    }
    // 定義一個虛擬頭結點
    private Node dummyHead, tail;
    private int size;

    public DummyHeadLinkedListQueue(){
        this.dummyHead = new Node(null, null);
        this.tail = null;
        this.size = 0;
    }

    @Override
    public int getSize() {
        return this.size;
    }

    public Boolean isEmpty(){
        return this.size == 0;
    }

    @Override
    public int getCapacity() {
        throw new IllegalArgumentException("LinkedList cannot getCapacity!");
    }

    @Override
    public void enqueue(E value) {
        //  開始入隊列(從隊列的尾部進行入隊列)
        // 1. 構造插入的節點
        Node node  = new Node(value);
        // 2. 尾部插入節點        //bug : 由於默認的時候 tail為null,此時如果直接訪問tail.next 是錯誤的
        if (tail == null) {
            dummyHead.next = node;
            // 說明此時隊列為空的
            tail = node;
        } else {
            tail.next = node;
            // 3. 尾部節點向后移動
            tail = tail.next;
        }
        // 4. 隊列長度增加
        size++;

    }


    @Override
    public E dequeue() {
        // 元素出隊列從鏈表的頭部出去
        // 1. 獲取頭結點的位置
        Node head = dummyHead.next;
        // 緩存出隊的元素
        Node retNode = head;
        // 2. 移動節點
        dummyHead.next = head.next;
        // 4. 更新容量
        size--;

        head = null;
        if (isEmpty())
            tail = null;            // bug修復

        return (E) retNode;
    }


    @Override
    public E getFront() {
        return null;
    }


    @Override
    public String toString(){
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("Queue head:");
        Node cur = dummyHead.next;
        while (cur != null){
            stringBuilder.append(cur + "->");
            cur = cur.next;
        }
        return stringBuilder.append("null tail").toString();
    }
}

  

以上就是創建隊列實現的5中方式,每種方式都有各自的特點,就做個簡單總結吧!  

隊列的時間復雜度分析:

 

+ 隊列Queue[數組隊列]
void enqueue(E) O(1) 均攤復雜度
E dequeue() O(n) 移出隊列首部的元素,需要其他元素向前補齊
E getFront() O(1)
void dequeue() O(1)
Int getSize() O(1)
bool isEmpty() O(1)
+ 循環隊列
void enqueue(E) O(1) 均攤復雜度
E dequeue() O(1)
E getFront() O(1)
void dequeue() O(1)
Int getSize() O(1)
bool isEmpty() O(1)

以上就是對於隊列實現的幾種方式的總結了。

 

  

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM