隊列
- 先進先出,後進後出
- 和棧的入棧push、出棧pop類似,隊列提供入隊enqueue、出隊dequeue兩種操作,也是一種操作受限的線性表數據結構
- 常用:循環隊列、阻塞隊列、併發隊列
- 基於數組:順序隊列
- 基於鏈表:鏈式隊列
順序隊列
//java實現一個順序隊列
public class ArrayQueue{
private String[] items;
//數組大小
private int n = 0;
private int head = 0;
private int tail = 0;
//構造
public ArrayQueue(int capacity){
this.items = new String[capacity];
this.n = capacity;
}
//入隊enqueue
public boolean enqueue(String item){
if(tail==n){
//隊列滿
return false;
}else{
items[tail] = item;
++tail;
return true;
}
}
//出隊dequeue
public String dequeue(){
if(head==tail){
//隊列空
return null;
}else{
String res = items[head];
++head;
return res;
}
}
}
- 和棧不同的是,棧只有一個棧頂指針,出入都在棧頂。
- 而隊列需要頭指針head和尾指針tail,head用於出隊,tail用於入隊。
問題:
tail已經移到隊列最尾部,head經過幾次出隊在數組中間,此時隊列的數組前面還是空的,但卻無法做入隊操作?
**答:**參考數組刪除元素,數據搬移問題的解決辦法,通過標記出隊的元素,當隊尾沒有空間但隊頭有空間,一次性清楚被標記的元素,並做數據搬移。
修改入隊enqueue()方法:
public boolean enqueue(String item){
if(tail==n){
//隊尾沒有空間
if(head==0){
//隊頭沒有空間
return false;
}else{
//隊頭有空間
//數據搬移
for(int i=head;i<tail;++i){
items[i-head] = items[i];
}
tail = tail-head;
head = 0;
}
}
}
出隊: O(1)
入隊: 最好O(1),最壞O(n),均攤O(1)
循環隊列
- 當隊尾沒有空間時,隊頭還有空間,tail指向下標0。
- 例如:隊列容量n爲9,某時tail指向8,當有元素入隊將元素放在下標8的位置,tail此時不指向9,而時指向0;
- 假如此時head=0,tail也即將指向0,那麼此時隊列在入隊操作後就滿了;
問題關鍵在於 怎樣確定隊空和隊滿?
非循環隊列:隊空 head=tail ,隊滿 tail=n
循環隊列: 隊空 head=tail, 隊滿 (tail+1)%n=head
//順序循環隊列
public class CircularQueue {
// 數組:items,數組大小:n
private String[] items;
private int n = 0;
// head表示隊頭下標,tail表示隊尾下標
private int head = 0;
private int tail = 0;
// 申請一個大小爲capacity的數組
public CircularQueue(int capacity) {
items = new String[capacity];
n = capacity;
}
// 入隊
public boolean enqueue(String item) {
// 隊列滿了
if ((tail + 1) % n == head) return false;
items[tail] = item;
tail = (tail + 1) % n;
return true;
}
// 出隊
public String dequeue() {
// 如果head == tail 表示隊列爲空
if (head == tail) return null;
String ret = items[head];
head = (head + 1) % n;
return ret;
}
}
阻塞隊列
- 阻塞隊列其實就是在隊列基礎上增加了阻塞操作。
- 在隊列爲空的時候,從隊頭取數據會被阻塞,直到隊列中有了數據才能返回;
- 如果隊列已經滿了,則插入數據的操作就會被阻塞,直到隊列中有空閒位置後再插入數據。
併發隊列
- 線程安全的隊列我們叫作併發隊列。
- 最簡單直接的實現方式是直接在 enqueue()、dequeue() 方法上加鎖,但是鎖粒度大併發度會比較低,同一時刻僅允許一個存或者取操作。
- 實際上,基於數組的循環隊列,利用 CAS 原子操作,可以實現非常高效的併發隊列。
- 這也是循環隊列比鏈式隊列應用更加廣泛的原因。
熄燈
隊列應用場景:
- 對於大部分資源有限的場景,當沒有空閒資源時,基本上都可以通過“隊列”這種數據結構來實現請求排隊。
- 例如:線程池,數據庫連接池等。