佇列是一種操作受限的線性表資料結構。
佇列最大的特點就是先進先出。
最基本的操作:入隊 enqueue(),放乙個資料到佇列尾部;出隊 dequeue(),從佇列頭部取乙個元素。
用陣列實現的佇列叫順序佇列,用鍊錶實現的佇列叫鏈式佇列。
佇列需要兩個指標:乙個是 head 指標,指向隊頭;乙個是 tail 指標,指向隊尾。
隨著不停地進行入隊、出隊操作,head 和 tail 都會持續往後移動。當 tail 移動到最右邊,即使陣列中還有空閒空間,也無法繼續往佇列中新增資料了。實際上,我們在出隊時可以不用搬移資料。如果沒有空閒空間了,只需要在入隊時,再集中觸發一次資料的搬移操作。出隊函式 dequeue() 保持不變,我們稍加改造一下入隊函式 enqueue() 的實現,當佇列的 tail 指標移動到陣列的最右邊後,如果有新的資料入隊,我們可以將 head 到 tail 之間的資料,整體搬移到陣列中 0 到 tail-head 的位置。
基於鍊錶的實現,同樣需要兩個指標:head 指標和 tail 指標。分別指向鍊錶的第乙個結點和最後乙個結點。
入隊時:tail->next= new_node; tail = tail->next;
出隊時:head = head->next;
迴圈佇列,原本陣列是有頭有尾的,是一條直線。把首尾相連,扳成了乙個環。
在陣列實現佇列的時候,會有資料搬移操作,要想解決資料搬移的問題,需要像環一樣的迴圈佇列。
要想寫出沒有 bug 的迴圈佇列的實現**,最關鍵的是,確定好隊空和隊滿的判定條件。
隊列為空的判斷條件仍然是head == tail
。
當隊滿時,(tail+1)%n=head
。 tail 指向的位置實際上是沒有儲存資料的。所以,迴圈佇列會浪費乙個陣列的儲存空間。
阻塞佇列
阻塞佇列就是在佇列基礎上增加了阻塞操作。
在隊列為空的時候,從隊頭取資料會被阻塞。因為此時還沒有資料可取,直到佇列中有了資料才能返回;如果佇列已經滿了,那麼插入資料的操作就會被阻塞,直到佇列中有空閒位置後再插入資料,然後再返回。
基於阻塞佇列實現的「生產者 - 消費者模型」,可以有效地協調生產和消費的速度。
併發佇列
執行緒安全的佇列,叫作併發佇列。
最簡單直接的實現方式是直接在 enqueue()、dequeue() 方法上加鎖,但是鎖粒度大併發度會比較低,同一時刻僅允許乙個存或者取操作。
實際上,基於陣列的迴圈佇列,利用 cas 原子操作,可以實現非常高效的併發佇列。這也是迴圈佇列比鏈式佇列應用更加廣泛的原因。
一般有兩種處理策略。第一種是非阻塞的處理方式,直接拒絕任務請求;
另一種是阻塞的處理方式,將請求排隊,等到有空閒執行緒時,取出排隊的請求繼續處理。
基於鍊錶的實現方式,可以實現乙個支援無限排隊的無界佇列(unbounded queue),但是可能會導致過多的請求排隊等待,請求處理的響應時間過長。所以,針對響應時間比較敏感的系統,基於鍊錶實現的無限排隊的執行緒池是不合適的。
基於陣列實現的有界佇列(bounded queue),佇列的大小有限,所以執行緒池中排隊的請求超過佇列大小時,接下來的請求就會被拒絕,這種方式對響應時間敏感的系統來說,就相對更加合理。不過,設定乙個合理的佇列大小,也是非常有講究的。佇列太大導致等待的請求太多,佇列太小會導致無法充分利用系統資源、發揮最大效能。
除了執行緒池這種池結構會用到佇列排隊請求,你還知道有哪些類似的池結構或者場景中會用到佇列的排隊請求呢?
分布式應用中的訊息佇列,也是一種佇列結構
併發佇列:關於如何實現無鎖併發佇列,網上有非常多的討論。對這個問題,你怎麼看呢?
考慮使用 cas 實現無鎖佇列,則在入隊前,獲取 tail 位置,入隊時比較tail是否發生變化,如果否,則允許入隊,反之,本次入隊失敗。出隊則是獲取 head 位置,進行 cas。
資料結構與演算法解析 佇列篇
先進者先出,這就是典型的 佇列 佇列跟棧非常相似,支援的操作也很有限,也是一種操作受限的線性表資料結構。最基本的操作也是兩個 跟棧一樣,佇列可以用陣列來實現,也可以用鍊錶來實現。用陣列實現的棧叫作順序棧,用鍊錶實現的棧叫作鏈式棧。同樣,用陣列實現的佇列叫作順序佇列,用鍊錶實現的佇列叫作鏈式佇列。2....
資料結構與演算法 基礎資料結構 佇列實現
在學習佇列的實現過程中,跟著教程自己手寫了佇列的實現,理解佇列的先進先出原理。以及略微複雜的迴圈佇列形成的乙個閉環,略微吃力,還需努力,詳細說明在注釋 package com.zhouyou.queue 普通佇列的實現 public class arrayqueue public void push...
資料結構與演算法 基礎演算法篇 遞迴
遞迴是一種非常高效 簡潔的編碼技巧,一種應用非常廣泛的演算法,比如dfs深度優先搜尋 前中後序二叉樹遍歷等都是使用遞迴。方法或函式呼叫自身的方式稱為遞迴呼叫,呼叫稱為遞,返回稱為歸。基本上,所有的遞迴問題都可以用遞推公式來表示,比如 f n f n 1 1 f n f n 1 f n 2 f n n...