本文整理來源 《輕鬆學算法——互聯網算法面試寶典》/趙燁 編著前端
什麼是隊列?隊列就是一個隊伍。隊列和棧同樣,由一段連續的存儲空間組成,是一個具備自身特殊規則的數據結構。棧是後進先出的規則,隊列恰好相反,是一個先進先出(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個元素,採用的是初始化數組時多設置一個位置來解決問題的;也能夠經過增長一個變量來記錄元素的個數去解決問題,不須要兩個標記去肯定是隊空仍是隊滿,元素也能放滿而不用空出一位了。
隊列的特色就是先進先出。出隊的一頭是隊頭,入隊的一頭是隊尾。固然,隊列通常都會規定一個有限的長度,叫作隊長。
隊列在實際開發當中很經常使用。在通常程序中會將隊列做爲緩衝器或者解藕使用。
某品牌的手機推出新型號,想要購買就須要上網預定,等到了開搶時間就得趕忙打開網頁守着,瘋狂刷新頁面。瘋狂的點擊搶購按鈕。通常在每次秒殺活動中提供的手機只有幾千部。假設有兩百萬的人搶購,那麼從開搶的這一秒,兩百萬人都開始向服務器發送請求。若是服務器都能直接處理請求,把搶購結果馬上告訴用戶,同時爲請購成功的用戶生成訂單,讓用戶付款購買手機,則這對服務器的要求很高,很難實現。能夠採用排隊的方式解決。把這些請求按順序放入隊列的隊尾中,而後提示用戶「正在排隊中......」,接下來用戶開始排隊:並且這個對列的另外一端,也就是隊頭會有一些服務器去處理,根據前後順序告知用戶搶購結果。
這就出現了搶購手機時,搶購界面稍後纔會告訴咱們搶購結果的狀況。
這種方式也叫作異步處理。異步與同步是相對的。同步是在一個調用執行完成以後,等待調用結束返回;而異步不會馬上返回結果,返回結果的時間是不可預料的,在另外一端的服務器處理完以後纔會有結果,如何通知執行的結果又是另外一回事。
這個模式就像有一個傳送帶,生產者在傳送帶這頭將生產的貨物放上去,消費者在另外一頭逐個地將貨物從傳送帶上取下來。這種設計設計模式的原理也比較簡單,即存在一個隊列,若干個生產者同時向隊列中添加元素,而後若干個消費者從隊列中獲取元素。
對於生產者和消費者的設計模式來講,有一點很是重要,那就是生產速度要和消費的速度持平。若是生產太快,而消費得太慢,那麼隊列就會很長。而對於計算機來講,隊列太長所佔用的空間也會較大。