死磕算法第二彈——棧、隊列、鏈表(2)

本文整理來源 《輕鬆學算法——互聯網算法面試寶典》/趙燁 編著前端

隊列

什麼是隊列

什麼是隊列?隊列就是一個隊伍。隊列和棧同樣,由一段連續的存儲空間組成,是一個具備自身特殊規則的數據結構。棧是後進先出的規則,隊列恰好相反,是一個先進先出(FIFO,First In First Out)或者說是後進後出(LILO,Last In Last Out)的數據結構。面試

隊列是一種受限的數據結構,插入操做只能從一端操做,這一端叫作隊尾;二移除操做也只能從另外一端操做,這一段叫做對頭。算法

咱們將沒有元素的隊列稱爲空隊。往隊列中插入元素的操做叫做入隊,相應的,從隊列中移除元素的操做叫做出隊。設計模式

通常而言,隊列的實現有兩種方式:數組和鏈表。用數組實現隊列有兩種方式,一種是順序隊列,一種是循環隊列。數組

用數組實現隊列,若出現隊列滿了的狀況,則這時就算有新的元素須要入隊,也沒有位置。此時通常的選擇是要麼丟掉,要麼等待,等待時間由程序控制。服務器

隊列的存儲結構

順序隊列會有兩個標記,一個是對頭位置(head),一個是下一個元素能夠插入的隊尾位置(tail)。一開始兩個標記都指向數組下表爲0的位置。數據結構

在插入元素以後,tail標記就會加1,如入隊三個元素,分別是A、B、C,則當前標記即存儲狀況。head指向0,tail指向3。異步

當head爲0時,tail爲3.接下來進行出隊操做,出隊一個元素,head指向的位置則加1。好比進行一次出隊操縱以後,順序隊列存儲狀況。head指向1,tail指向3。測試

所以,在順序隊列中,對隊列中元素的個數咱們能夠用tail減去head計算。當head與tail相等時,隊列爲空隊,當tail達到數組的長度,也就是隊列存儲以外的位置時,說明這個隊列已經沒法容納其餘元素入隊了。空間是否滿了?並無,因爲兩個標記只增不減,因此兩個標記最終都會到數組的最後一個元素以外,這是雖然數組是空的,但也沒法再往隊列里加入元素了。this

當隊列中沒法再加入元素時,咱們稱之爲「上溢」; 當順序隊列還有空間卻沒法入隊時,咱們稱之爲「假上溢」;若是空間真的滿了,則咱們稱之爲「真上溢」;若是隊列是空的,則執行出隊操做,此時隊列裏沒有元素,能不能出隊,咱們稱之爲「下溢」。

怎麼解決順序隊列的「假上溢」問題,這時就須要採用循環隊列了。

當順序隊裏出現假上溢時,其實數組前端還有空間,咱們能夠不把標記指向數組外的地方,只須要把這個標記從新指向開始處就可以解決。想一想一下這個數組首尾相接,成爲一個圈。存儲結構仍是在一個數組上。

通常而言。咱們在對head或者tail加1時,爲了方便,可直接對結果取餘數組長度,獲得咱們徐亞的數組長度。另外因爲順序隊列存在「假上溢」的問題,全部在實際使用過程當中都是使用循環隊列來實現的。

可是循環隊列中會出現這樣一種狀況:當隊列沒有元素時,head等於tail,而當隊列滿了時,head也等於tail。爲了區分這兩種狀態,通常在循環隊列中規定隊列長度只能爲數組總長度減1,即有一個位置不放元素。所以,當head等於tail時,說明隊列爲空隊,而當head等於(tail+1)%length時,說明隊滿。

代碼以下

public class ArrayQueue<T> {

    private final Object[] items;

    private int head = 0;

    private int tail = 0;

    /**
     * 初始化隊列
     *
     * @param capacity 隊列長度
     */
    public ArrayQueue(int capacity) {
        this.items = new Object[capacity];
    }

    /**
     * 入隊
     *
     * @param item 入隊元素
     * @return 是否入隊
     */
    public boolean put(T item) {
        if (head == (tail + 1) % items.length) {
            //表示隊滿
            return false;
        }
        items[tail] = item;
        //tail 標記日後移一位
        tail = (tail + 1) % items.length;
        return true;
    }

    /**
     * 獲取隊列頭元素,不出隊
     *
     * @return 隊列頭元素
     */
    @SuppressWarnings("unchecked")
    public T peek() {
        if (head == tail) {
            return null;
        }
        return (T)items[head];
    }

    /**
     * 出隊
     *
     * @return 頭部元素
     */
    @SuppressWarnings("unchecked")
    public T poll() {
        if (head == tail) {
            return null;
        }
        T item = (T)items[head];
        //把沒有用的元素賦空值,固然也能夠不設置,標記移動了,以後會被覆蓋。儘可能仍是設置null
        items[head] = null;
        //head標記日後移動一位
        head = (head + 1) % items.length;
        return item;
    }

    public boolean isFull() {
        return head == (tail + 1) % items.length;
    }

    public boolean isEmpty() {
        return head == tail;
    }

    /**
     * 隊列元素數
     *
     * @return 隊列元素數
     */
    public int size() {
        if (tail >= head) {
            return tail - head;
        }else {
            return tail + items.length - head;
        }
    }

}

測試代碼以下

public class ArrayQueueTest {

    @Test
    public void main(){

        ArrayQueue<String> queue = new ArrayQueue<>(4);
        Assert.assertTrue("添加A失敗",queue.put("A"));
        Assert.assertTrue("添加B失敗",queue.put("B"));
        Assert.assertTrue("添加C失敗",queue.put("C"));

        Assert.assertTrue("添加D成功",!queue.put("D"));

        //隊列已滿,而且D元素沒有入隊成功
        Assert.assertTrue("隊列未滿",queue.isFull());

        Assert.assertEquals("隊列中元素數不爲3",3,queue.size());

        //獲取頭元素但不出隊
        Assert.assertEquals("頭元素不爲A","A",queue.peek());

        Assert.assertEquals("出隊A失敗","A",queue.poll());
        Assert.assertEquals("出隊B失敗","B",queue.poll());
        Assert.assertEquals("出隊C失敗","C",queue.poll());

        //隊列爲空隊
        Assert.assertTrue("隊列不爲空",queue.isEmpty());
    }

}

以上代碼中,聲明爲4,可是使用的時候只能放入3個元素,採用的是初始化數組時多設置一個位置來解決問題的;也能夠經過增長一個變量來記錄元素的個數去解決問題,不須要兩個標記去肯定是隊空仍是隊滿,元素也能放滿而不用空出一位了。

隊列的特色

隊列的特色就是先進先出。出隊的一頭是隊頭,入隊的一頭是隊尾。固然,隊列通常都會規定一個有限的長度,叫作隊長。

隊列的使用場景

隊列在實際開發當中很經常使用。在通常程序中會將隊列做爲緩衝器或者解藕使用。

某品牌手機在線秒殺用到的隊列

某品牌的手機推出新型號,想要購買就須要上網預定,等到了開搶時間就得趕忙打開網頁守着,瘋狂刷新頁面。瘋狂的點擊搶購按鈕。通常在每次秒殺活動中提供的手機只有幾千部。假設有兩百萬的人搶購,那麼從開搶的這一秒,兩百萬人都開始向服務器發送請求。若是服務器都能直接處理請求,把搶購結果馬上告訴用戶,同時爲請購成功的用戶生成訂單,讓用戶付款購買手機,則這對服務器的要求很高,很難實現。能夠採用排隊的方式解決。把這些請求按順序放入隊列的隊尾中,而後提示用戶「正在排隊中......」,接下來用戶開始排隊:並且這個對列的另外一端,也就是隊頭會有一些服務器去處理,根據前後順序告知用戶搶購結果。

這就出現了搶購手機時,搶購界面稍後纔會告訴咱們搶購結果的狀況。

這種方式也叫作異步處理。異步與同步是相對的。同步是在一個調用執行完成以後,等待調用結束返回;而異步不會馬上返回結果,返回結果的時間是不可預料的,在另外一端的服務器處理完以後纔會有結果,如何通知執行的結果又是另外一回事。

生產者和消費者模式

這個模式就像有一個傳送帶,生產者在傳送帶這頭將生產的貨物放上去,消費者在另外一頭逐個地將貨物從傳送帶上取下來。這種設計設計模式的原理也比較簡單,即存在一個隊列,若干個生產者同時向隊列中添加元素,而後若干個消費者從隊列中獲取元素。

對於生產者和消費者的設計模式來講,有一點很是重要,那就是生產速度要和消費的速度持平。若是生產太快,而消費得太慢,那麼隊列就會很長。而對於計算機來講,隊列太長所佔用的空間也會較大。

相關文章
相關標籤/搜索