# Data Structure / Queue
## 1. 基本概念
佇列(Queue)是一種遵循「先進先出」(First-In-First-Out, FIFO)原則的資料結構。就像排隊買電影票一樣,先到的人先被服務,後到的人需要等待前面的人完成後才能被服務。
在佇列中:
- 新元素永遠從尾端(Rear / Back)加入
- 移除元素永遠從前端(Front)進行
- 第一個被加入的元素會是第一個被取出的元素
## 2. 基本操作
- **`createQueue()`**:建立一個新的空佇列。
- **`enqueue()`**:將新元素加入佇列尾端。
- **`dequeue()`**:從佇列前端移除並回傳元素。
- **`peek()`**:查看佇列前端的元素但不移除。
## 3. 佇列的實作方式
### 3.1 使用陣列實作(Array-Based Implementation)
以下是使用陣列實作佇列的範例程式碼:
```java=
class QueueArrayBased {
private int MAX_QUEUE = 50;
private Object[] items;
private int front, back, count;
public QueueArrayBased() {
items = new Object[MAX_QUEUE];
front = 0;
back = MAX_QUEUE - 1;
count = 0;
}
// helper methods
public boolean isEmpty() {
return count == 0;
}
public boolean isFull() {
return count == MAX_QUEUE;
}
// enqueue
public void enqueue(Object newItem) throws QueueException {
if (!isFull()) {
back = (back + 1) % MAX_QUEUE;
items[back] = newItem;
count++;
} else {
throw new QueueException("Queue is full");
}
}
// dequeue
public Object dequeue() throws QueueException {
if (!isEmpty()) {
Object queueFront = items[front];
front = (front + 1) % MAX_QUEUE;
count--;
return queueFront;
} else {
throw new QueueException("Queue is Empty");
}
}
// peek
public Object peek() throws QueueException {
if (!isEmpty()) {
return items[front];
} else {
throw new QueueException("Queue is Empty");
}
}
}
class QueueException extends Exception {
public QueueException(String message) {
super(message);
}
}
public class DS {
public static void main(String[] args) {
QueueArrayBased queue = new QueueArrayBased();
try {
// Enqueue items
queue.enqueue("Item 1");
queue.enqueue("Item 2");
queue.enqueue("Item 3");
// Peek at the front item
System.out.println("Front item: " + queue.peek());
// Dequeue items
System.out.println("Dequeued: " + queue.dequeue());
System.out.println("Dequeued: " + queue.dequeue());
// Peek again
System.out.println("Front item after dequeues: " + queue.peek());
// Dequeue the last item
System.out.println("Dequeued: " + queue.dequeue());
// Attempt to dequeue from empty queue
System.out.println("Dequeued: " + queue.dequeue());
} catch (QueueException e) {
System.err.println("Queue operation error: " + e.getMessage());
}
}
}
```
### 3.2 使用鏈結串列實作(Linked List Implementation)
以下是使用循環鏈結串列實作佇列的範例:
```java=
class LinkedListBasedQueue {
private class Node {
Object item;
Node next;
Node(Object item) {
this.item = item;
this.next = null;
}
}
private Node lastNode;
public LinkedListBasedQueue() {
lastNode = null;
}
// helper methods
public boolean isEmpty() {
return lastNode == null;
}
// enqueue
public void enqueue(Object item) {
Node newNode = new Node(item);
if (isEmpty()) {
newNode.next = newNode;
} else {
newNode.next = lastNode.next;
lastNode.next = newNode;
}
lastNode = newNode;
}
// dequeue
public Object dequeue() throws QueueException {
if (!isEmpty()) {
Node firstNode = lastNode.next;
if (firstNode == lastNode) {
lastNode = null;
} else {
lastNode.next = firstNode.next;
}
return firstNode.item;
} else {
throw new QueueException("Queue is Empty.");
}
}
public Object peek() throws QueueException {
if (!isEmpty()) {
return lastNode.next.item;
} else {
throw new QueueException("Queue is Empty.");
}
}
}
class QueueException extends Exception {
public QueueException(String message) {
super(message);
}
}
public class DS {
public static void main(String[] args) {
LinkedListBasedQueue queue = new LinkedListBasedQueue();
try {
// Enqueue items
queue.enqueue("Item 1");
queue.enqueue("Item 2");
queue.enqueue("Item 3");
// Peek at the front item
System.out.println("Front item: " + queue.peek());
// Dequeue items
System.out.println("Dequeued: " + queue.dequeue());
System.out.println("Dequeued: " + queue.dequeue());
// Peek again
System.out.println("Front item after dequeues: " + queue.peek());
// Dequeue the last item
System.out.println("Dequeued: " + queue.dequeue());
// Attempt to dequeue from an empty queue
System.out.println("Dequeued: " + queue.dequeue());
} catch (QueueException e) {
System.err.println("Queue operation error: " + e.getMessage());
}
}
}
```
## 4. 特殊類型的佇列
### 4.1 循環佇列(Circular Queue)
循環佇列使用固定大小的陣列,但是以循環方式使用空間。當到達陣列尾端時,下一個元素會被放置在陣列開始的位置(如果該位置為空)。
```java=
public class CircularQueue {
private Object[] queue;
private int front, rear;
private boolean flag; // 用於判斷佇列是否已滿
private int size;
public CircularQueue(int size) {
this.size = size;
queue = new Object[size];
front = rear = 0;
flag = false;
}
public void enqueue(Object item) throws QueueException {
if (flag) {
throw new QueueException("佇列已滿");
}
queue[rear] = item;
rear = (rear + 1) % size;
if (rear == front) {
flag = true;
}
}
public Object dequeue() throws QueueException {
if (rear == front && !flag) {
throw new QueueException("佇列為空");
}
Object item = queue[front];
front = (front + 1) % size;
flag = false;
return item;
}
}
class QueueException extends Exception {
public QueueException(String message) {
super(message);
}
}
```
### 4.2 雙向佇列(Double-ended Queue,Deque)
雙向佇列允許在佇列的兩端(前端和後端)都進行插入和刪除操作,提供了較高的靈活性。請直接參考程式碼中的函數:
```java=
public class Deque<T> {
private class Node {
T data;
Node prev;
Node next;
Node(T data) {
this.data = data;
this.prev = null;
this.next = null;
}
}
private Node front;
private Node rear;
private int size;
public Deque() {
front = rear = null;
size = 0;
}
public boolean isEmpty() {
return size == 0;
}
// 從前端加入元素
public void addFirst(T item) {
Node newNode = new Node(item);
if (isEmpty()) {
front = rear = newNode;
} else {
newNode.next = front;
front.prev = newNode;
front = newNode;
}
size++;
}
// 從後端加入元素
public void addLast(T item) {
Node newNode = new Node(item);
if (isEmpty()) {
front = rear = newNode;
} else {
rear.next = newNode;
newNode.prev = rear;
rear = newNode;
}
size++;
}
// 從前端移除元素
public T removeFirst() throws DequeException {
if (isEmpty()) {
throw new DequeException("雙向佇列為空");
}
T data = front.data;
front = front.next;
if (front == null) {
rear = null;
} else {
front.prev = null;
}
size--;
return data;
}
// 從後端移除元素
public T removeLast() throws DequeException {
if (isEmpty()) {
throw new DequeException("雙向佇列為空");
}
T data = rear.data;
rear = rear.prev;
if (rear == null) {
front = null;
} else {
rear.next = null;
}
size--;
return data;
}
// 查看前端元素
public T getFirst() throws DequeException {
if (isEmpty()) {
throw new DequeException("雙向佇列為空");
}
return front.data;
}
// 查看後端元素
public T getLast() throws DequeException {
if (isEmpty()) {
throw new DequeException("雙向佇列為空");
}
return rear.data;
}
}
// customize Exception
class DequeException extends Exception {
public DequeException(String message) {
super(message);
}
}
```
### 4.3 優先佇列(Priority Queue)
優先佇列中的元素都會被賦予優先次序。具有較高優先權的元素會比優先權較低的元素先被處理,即使它是後來才加入佇列的。
#### 4.3.1 特點
1. 元素都具有優先權值
2. 取出元素時會選擇優先權最高的
3. 相同優先權的元素按照先進先出原則處理
4. 常用堆積(Heap)資料結構來實作
以下是使用最小堆積(Min Heap)實作優先佇列的範例:
```java=
import java.util.ArrayList;
class PriorityQueue<T extends Comparable<T>> {
private class PQNode implements Comparable<PQNode> {
T data;
int priority;
PQNode(T data, int priority) {
this.data = data;
this.priority = priority;
}
@Override
public int compareTo(PQNode other) {
return this.priority - other.priority;
}
}
private ArrayList<PQNode> heap;
public PriorityQueue() {
heap = new ArrayList<>();
}
public boolean isEmpty() {
return heap.isEmpty();
}
// 加入具有優先權的元素
public void enqueue(T item, int priority) {
PQNode newNode = new PQNode(item, priority);
heap.add(newNode);
siftUp(heap.size() - 1);
}
// 取出優先權最高的元素
public T dequeue() throws PriorityQueueException {
if (isEmpty()) {
throw new PriorityQueueException("優先佇列為空");
}
T result = heap.get(0).data;
int lastIdx = heap.size() - 1;
heap.set(0, heap.get(lastIdx));
heap.remove(lastIdx);
if (!isEmpty()) {
siftDown(0);
}
return result;
}
// 向上調整堆積
private void siftUp(int index) {
while (index > 0) {
int parentIdx = (index - 1) / 2;
if (heap.get(index).compareTo(heap.get(parentIdx)) < 0) {
swap(index, parentIdx);
index = parentIdx;
} else {
break;
}
}
}
// 向下調整堆積
private void siftDown(int index) {
int size = heap.size();
while (true) {
int smallest = index;
int leftChild = 2 * index + 1;
int rightChild = 2 * index + 2;
if (leftChild < size && heap.get(leftChild).compareTo(heap.get(smallest)) < 0) {
smallest = leftChild;
}
if (rightChild < size && heap.get(rightChild).compareTo(heap.get(smallest)) < 0) {
smallest = rightChild;
}
if (smallest == index) {
break;
}
swap(index, smallest);
index = smallest;
}
}
private void swap(int i, int j) {
PQNode temp = heap.get(i);
heap.set(i, heap.get(j));
heap.set(j, temp);
}
}
// customize Exception
class PriorityQueueException extends Exception {
public PriorityQueueException(String message) {
super(message);
}
}
```
#### 4.3.2 優先佇列的應用場景
1. **作業系統處理**
- 處理程序排程
- 中斷處理
- 資源分配
2. **網路流量管理**
- 封包優先權處理
- QoS(服務品質)保證
3. **演算法應用**
- Dijkstra 最短路徑演算法
- Prim 最小生成樹演算法
- 赫夫曼編碼
4. **即時系統**
- 任務排程
- 即時事件處理
---
#### 所以泥在 Main Class 裡面就可以醬:
```java=
public class PriorityQueueDemo {
public static void main(String[] args) {
PriorityQueue<String> pq = new PriorityQueue<>();
// 加入具有不同優先權的工作
pq.enqueue("列印文件", 3);
pq.enqueue("系統更新", 1); // 較高優先權
pq.enqueue("備份資料", 2);
try {
// 優先處理優先權較高的工作
System.out.println("處理工作: " + pq.dequeue()); // 輸出:系統更新
System.out.println("處理工作: " + pq.dequeue()); // 輸出:備份資料
System.out.println("處理工作: " + pq.dequeue()); // 輸出:列印文件
} catch (PriorityQueueException e) {
System.out.println("錯誤:" + e.getMessage());
}
}
}
```
## 5. 佇列的應用
1. **作業系統工作排程**:
- 處理具有相同優先權的工作
- 印表機列印佇列(Printer Spooling)
2. **系統模擬**:
- 事件驅動模擬(Event-driven Simulation)
- 時間驅動模擬(Time-driven Simulation)
3. **緩衝區管理**:
- 輸入/輸出緩衝
- 資料流處理
4. **演算法應用**:
- 圖形走訪中的廣度優先搜尋(Breadth-First Search)
- 樹狀結構的層次走訪(Tree's Level-Order Traversal)
## 6. 效能考量
1. **空間使用效率**:
- 陣列實作:固定大小,可能造成空間浪費
- 鏈結串列實作:動態分配空間,較有彈性
2. **時間複雜度**:
- Enqueue 操作:O(1)
- Dequeue 操作:O(1)
- **`isEmpty()`**:O(1)
3. **實作選擇考量**:
- 如果佇列大小固定且可預期,使用陣列實作
- 如果佇列大小動態變化,使用鏈結串列實作
- 如果需要頻繁的插入 / 刪除,使用鏈結串列實作