佇列初相識
什麼是佇列
棧是先進者後出,後進者先出,而佇列是先進者先出,對於在記憶體中如何儲存並沒有要求,如果通過陣列來實現叫做順序佇列,如果通過連結串列實現,則叫做鏈式佇列,可以看出上圖就是順序佇列,就好比地鐵過安檢時,每個人排隊,依次進入檢查
順序佇列
佇列的特點是先進者先出,所以需要兩個指標,一個指標指向第一條資料,另一個指向最後一條資料
因為陣列建立時便已經指定大小,所以順序佇列的儲存空間有限,為了提高空間的利用率,每當有出對操作時,我們可以進行資料搬移
但是如果頻繁的進行資料搬移,會使得效能降低,所以當沒有空閒空間時,我們只需要在入隊時,集中觸發一次資料的搬移操作即可.
public class ArrayQueue<E> { // 陣列:items,陣列大小:n private E[] items; private int n = 0; // head表示隊頭下標,tail表示隊尾下標 private int head = 0; private int tail = 0; // 申請一個大小為capacity的陣列 public ArrayQueue(int capacity) { items = (E[])new Object[capacity]; n = capacity; } // 入隊操作,將item放入隊尾 public boolean enqueue(E item) { // tail == n表示佇列末尾沒有空間了 if (tail == n) { // tail ==n && head==0,表示整個佇列都佔滿了 if (head == 0) return false; // 資料搬移 for (int i = head; i < tail; ++i) { items[i-head] = items[i]; } // 搬移完之後重新更新head和tail tail -= head; head = 0; } items[tail] = item; ++tail; return true; } // 出隊 public E dequeue() { // 如果head == tail 表示佇列為空 if (head == tail) return null; E ret = items[head]; ++head; return ret; } }
迴圈佇列
上面使用陣列實現了佇列的基本操作,但是總會涉及到搬移資料,這樣入隊操作的效能就會受到影響,那有沒有辦法避免資料搬移資料呢?
從上圖我們可以看出這是一個大小為9,head=1,tail=5的佇列,再有資料入隊時,將會放到下標為5的位置,tail+1,但是當tail=8時,如何處理呢?
從圖中我們可以很明顯的看出當有資料入隊時,資料放到下標為8的位置,而tail則要更新為0,轉化為程式碼時,我們要如何判斷呢?
使用陣列實現佇列時,個人覺得:最關鍵的是,確定隊空和隊滿的判定條件.
在用陣列實現的非迴圈佇列中,隊滿的判斷條件是 tail == n,隊空的判斷條件是 head == tail。那針對迴圈佇列,如何判斷隊空和隊滿呢?
佇列為空的判斷條件仍然是 head == tail。但佇列滿的判斷條件是什麼呢?
在一般情況下,隊滿的判斷條件為 tial + 1 = head
但是有個特殊情況, head = 0, tail = n-1, 如下圖所示
綜上所述:
當head !=0時,
tail+1=head
當head =0時,
tail+1=n
因為tail+1的最大值為n
所以
(tail+1)%n=head
程式碼實現如下
public class CircularQueue<E> { // 陣列:items,陣列大小:n private E[] items; private int n = 0; // head表示隊頭下標,tail表示隊尾下標 private int head = 0; private int tail = 0; // 申請一個大小為capacity的陣列 public CircularQueue(int capacity) { items = (E[])new Object[capacity]; n = capacity; } // 入隊 public boolean enqueue(E item) { // 佇列滿了 if ((tail + 1) % n == head) return false; items[tail] = item; tail = (tail + 1) % n; return true; } // 出隊 public E dequeue() { // 如果head == tail 表示佇列為空 if (head == tail) return null; E ret = items[head]; head = (head + 1) % n; return ret; } }
連結串列佇列
public class QueueByList<E> {
// 佇列的隊首和隊尾
private Node head = null;
private Node tail = null;
// 入隊
public void enqueue(E value) {
if (tail == null) {
Node newNode = new Node(value, null);
head = newNode;
tail = newNode;
} else {
tail.next = new Node(value, null);
tail = tail.next;
}
}
// 出隊
public E dequeue() {
if (head == null) return null;
E value = (E) head.data;
head = head.next;
if (head == null) {
tail = null;
}
return value;
}
//結點
private static class Node<E> {
private E data;
private Node next;
public Node(E data, Node next) {
this.data = data;
this.next = next;
}
public E getData() {
return data;
}
}
}
阻塞佇列
阻塞佇列是一個支援兩個附加操作的佇列。
- 當佇列滿時,佇列會阻塞插入元素的執行緒,直到佇列不滿。
- 當佇列為空時,獲取元素的執行緒會等待佇列變為非空
阻塞佇列常用於生產者和消費者的場景,生產者是往佇列裡新增元素的執行緒,消費者是從佇列裡拿元素的執行緒。阻塞佇列就是生產者存放元素的容器,而消費者也只從容器裡拿元素。
JDK7提供了7個阻塞佇列。分別是
- ArrayBlockingQueue :一個由陣列結構組成的有界阻塞佇列。
- LinkedBlockingQueue :一個由連結串列結構組成的有界阻塞佇列。
- PriorityBlockingQueue :一個支援優先順序排序的無界阻塞佇列。
- DelayQueue:一個使用優先順序佇列實現的無界阻塞佇列。
- SynchronousQueue:一個不儲存元素的阻塞佇列。
- LinkedTransferQueue:一個由連結串列結構組成的無界阻塞佇列。
- LinkedBlockingDeque:一個由連結串列結構組成的雙向阻塞佇列。