隊列: 尾指針索引小於頭指針的擴容問題

  隊列,一種作爲構思算法的輔助數據結構,和棧相反,遵循FIFO即先進先出原則。

  爲了節省空間,提高底層數據結構的使用率(隊列可以通過數組實現,也可以通過鏈表實現),常見的做法和其他數據結構擴容不同,並不是尾指針移至末尾就進行擴容,而是將尾指針移至0,從頭開始(當然這要求底層數據結構如數組,起始的位置數據已經pop出去了,否則將進行擴容)。如下所示:

  如圖我們再次pop一個3的時候,並不像其他數據結構一樣,立即擴容,而是將隊尾指針移至0,將3這個元素push到0索引的位置:

  這樣做的根本目的就是爲了節省空間,當然這會在算法實現上帶來一系列難度。尤其在擴容的時候,假設我們在上圖所示隊列中,再push兩個元素3,那麼隊尾指針到達2,對頭指針不變,這個沒有什麼問題,但是在我們還想push一個元素的時候,這個時候如果像其他數據結構一樣,進行簡單的擴容是不能滿足的。如下:

  如果只是簡單的擴容,我們再想push元素的時候,隊尾指針上移,但是上移之後和隊頭指針衝突,我們這樣的擴容是不行的。

 

解決思路:

1.  和其他數據結構一樣,尾指針到達數組(以下都以數組實現隊列爲例)末尾的時候,不掉頭,直接進行擴容。雖然可能大部分程序猿實現隊列都是掉頭,但是我們要知道這樣做的根本目的是節省空間,而不是什麼規範和協議必須遵守。由此引來的問題就是空間浪費問題。

2. 那麼爲什麼大多數程序猿都選擇尾指針掉頭呢,因爲本文所提及的擴容問題,解決方案並不難,我們可以在copy的時候,將隊頭指針到數組末尾的數據和數組開始到隊尾指針的位置分開copy,雖然複製了兩次,但是數組複製我們都調用System.arraycopy,利用他們內存地址連續,快速操作,不用循環(可自行百度數組內部尋址)。

  如下所示,我們將隊頭至數組末尾的數據1,2,3,4,在擴容的時候,移至新數組的末尾,同時隊頭位置發生改變。

3. 爲了節省空間,我們可以在每次push的時候,調用一個是否需要縮減容量的處理方法,比如,如果當前隊列數組長度爲20,但是實際數據量只有10,我們可以進行一次容量縮減,如果實在有此需要,建議將數組容量的1/2作爲標準。

 

代碼實現:

package com.dzh.learn.dataStructuresAndAlgorithm.dataStructures;

import java.util.Arrays;

/**
 * 隊列
 * FIFO    first in first out  先進先出
 * 單向隊列
 * @author caiwm
 */
public class MyQueue {

    private int[] arr;

    private int defCapacity = 10;

    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    /*** 實際長度 */
    private int size;

    /*** 隊頭 先出 */
    private int queueHead;

    /*** 隊尾 後入 */
    private int queueEnd;

    public MyQueue(int initCapacity){
        if (initCapacity <= 0)
            throw new RuntimeException("Capacity must greater than zero.");
        arr = new int[initCapacity];
    }

    public MyQueue(){
        arr = new int[defCapacity];
    }

    public void push(int element){
        ensureCapacityInternal();
        // TODO 縮減整理 例如空閒空間大於1/2
        if (size == 0){
            queueHead = queueEnd = 0;
            arr[0] = element;
        } else{
            // 最上 返回0賦值
            if (queueEnd == (arr.length - 1)){
                arr[0] = element;
                queueEnd = 0;
            } else {
                arr[++queueEnd] = element;
            }
        }
        size++;
    }

    private void ensureCapacityInternal(){
        if (arr.length == size){
            grow();
        }
    }

    private void grow(){
        // int 最大值校驗
        int capacityAdd = ensureExplicitCapacityAdd();
        if (queueEnd < queueHead){
            // 尾指針掉頭 copy方案
            int[] arrNew = new int[arr.length + capacityAdd];
            int numMoved = arr.length - queueHead;
            System.arraycopy(arr, queueHead, arrNew, (queueHead + capacityAdd), numMoved);
            System.arraycopy(arr, 0, arrNew, 0, queueHead);
            queueHead += capacityAdd;
            arr = arrNew;
        } else {
            arr = Arrays.copyOf(arr, arr.length + capacityAdd);
        }
    }

    private int ensureExplicitCapacityAdd(){
        if (arr.length + 1 > MAX_ARRAY_SIZE)
            return ((arr.length + 1 > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE) -  arr.length;
        else
            return arr.length >> 1;
    }

    public int peek(){
        if (isEmpty())
            throwEmptyException();
        return arr[queueHead];
    }

    public int pop(){
        if (isEmpty())
            throwEmptyException();
        int i = arr[queueHead];
        arr[queueHead] = 0;  // 爲了大家看得更直觀,其實可以不需要,但是Object類型queue或者泛型queue,此處最好置爲null
        if (queueHead == (arr.length - 1)){
            queueHead = 0;
        } else {
            queueHead++;
        }
        size--;
        return i;
    }

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

    public void throwEmptyException(){
        throw new RuntimeException("Queue is empty.");
    }
}

  

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章