資料結構-Queue篇-1
前情提要
這篇文章是資料結構系列的第三篇,主要探討 Queue(佇列) 的定義、抽象資料型別(ADT)、以及不同的實作方式。今天的內容包括 線性隊列 的問題與解決方法、 環形隊列(Circular Queue) 的兩種實現,以及 鏈結串列實作的 Queue。文章還透過具體的範例程式碼展示了如何操作隊列中的新增(enqueue)和刪除(dequeue),幫助讀者更深入地理解隊列的運作與應用場景。
Queue 的定義
- A queue is an ordered list in which all insertions take place at one end and all deletions take place at the opposite end. Given a queue \(Q = (a_0, a_1, \dotsb, a_{n-1})\), \(a_0\) 我們稱之為 front element, \(a_{n-1}\) 我們稱之為 rear element,那麼 Queue 有 \(\text{First-In-First-Out (FIFO)}\) 的性質。
Queue ADT
結構 : Queue
物件 : a finite ordered list with zero or more
elements
函數定義
Queue CreateQ(max_queue_size) : 建立一個空的 queue 且最大容量為 max_queue_size。
Boolean IsFullQ(queue, max_queue_size) :
1
2
3
4if (number of elements in queue == max_queue_size)
return TRUE
else
return FALSEBoolean IsEmptyQ(queue) :
1
2
3
4if (queue == CreateQ(max_queue_size))
return TRUE
else
return FALSEQueue 中新增 element
Queue AddQ(queue, item) 此為
Horowitz
的寫法 :1
2
3
4if (IsFullQ(queue))
queue_full
else
insert item at rear of queue and return queueENQUEUE(Q, x) 此為
CLRS
中的寫法,此為circular queue
,稍後會說明到:1
2
3
4
5Q[Q.tail] = x
if Q.tail == Q.length
Q.tail = 1
else
Q.tail = Q.tail + 1
Queue 中刪除 element
Element DeleteQ(queue) 此為
Horowitz
的寫法:1
2
3
4if (IsEmptyQ(queue))
queue_empty
else
remove and return the item at front of queueDEQUEUE(Q) 此為
CLRS
中的寫法 :1
2
3
4
5
6x = Q[Q.head]
if Q.head == Q.length
Q.head = 1
else
Q.head = Q.head + 1
return x
Queue 的實作方式
Linear Array
實作程式碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
using namespace std;
class Queue {
private:
int front, rear, size;
int* arr;
public:
Queue(int maxSize) {
size = maxSize;
arr = new int[size];
front = -1;
rear = -1;
}
~Queue() {
delete[] arr;
}
bool isFull() {
return rear == size - 1;
}
bool isEmpty() {
return front == rear;
}
void enqueue(int item) {
if (isFull()) {
cout << "Queue is full, shifting elements to the left..." << endl;
shiftLeft();
}
if (rear == -1) {
front = 0;
}
arr[++rear] = item;
cout << "Enqueued: " << item << endl;
}
int dequeue() {
if (isEmpty()) {
cout << "Queue is empty." << endl;
return -1;
}
int item = arr[front];
front++;
cout << "Dequeued: " << item << endl;
return item;
}
void shiftLeft() {
for (int i = front; i <= rear; i++) {
arr[i - front] = arr[i];
}
rear = rear - front;
front = 0;
}
};
int main() {
Queue q(3);
q.enqueue(1);
q.enqueue(2);
q.enqueue(3);
q.enqueue(4);
q.dequeue();
q.dequeue();
q.enqueue(5);
q.enqueue(6);
return 0;
}輸出內容
1
2
3
4
5
6
7
8
9Enqueued: 1
Enqueued: 2
Enqueued: 3
Queue is full, shifting elements to the left...
Enqueued: 4
Dequeued: 1
Dequeued: 2
Enqueued: 5
Enqueued: 6這個方法有一個嚴重的問題如下圖 (圖 2),當 Job 進入或離開時,Queue 的索引(rear 和 front)會隨著作業的加入或刪除逐漸向右移動。當 \(rear\) 等於 \(\text{MAX_QUEUE_SIZE} - 1\) 時,這意味著隊列已滿。在這種情況下,必須通過
queue_full
操作將整個隊列左移,這樣 \(front\) 就會回到 \(-1\),而 \(rear\) 也需要重新計算,以確保隊列正確排列。
這是一個典型的線性隊列,而不是循環隊列。當隊列滿了並且刪除了一些元素時,隊列的空間無法立即重用,因此需要整體左移來重新排列元素,這也是該隊列的限制之一。這樣花的時間是 \(O(\text{MAX_QUEUE_SIZE})\),因為每次queue_full
都要將所有元素往左移動。
Array Circular queue
法一
實作程式碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
using namespace std;
class CircularQueue {
private:
int* queue;
int size;
int front, rear;
public:
CircularQueue(int n) {
size = n;
queue = new int[size];
front = 0;
rear = 0;
}
bool isFull() {
return (rear + 1) % size == front;
}
bool isEmpty() {
return front == rear;
}
void enqueue(int value) {
if (isFull()) {
cout << "Queue is full" << endl;
return;
}
queue[rear] = value;
rear = (rear + 1) % size;
}
int dequeue() {
if (isEmpty()) {
cout << "Queue is empty" << endl;
return -1;
}
int value = queue[front];
front = (front + 1) % size;
return value;
}
void display() {
if (isEmpty()) {
cout << "Queue is empty" << endl;
return;
}
int i = front;
while (i != rear) {
cout << queue[i] << " ";
i = (i + 1) % size;
}
cout << endl;
}
~CircularQueue() {
delete[] queue;
}
};
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
CircularQueue q(5);
q.enqueue(10);
q.enqueue(20);
q.enqueue(30);
q.enqueue(40);
q.display();
q.dequeue();
q.display();
q.enqueue(50);
q.display();
q.enqueue(60);
}輸出結果
1
2
3
410 20 30 40
20 30 40
20 30 40 50
Queue is full使用這個方法最多可以利用 \(n-1\) 的空間。
法二
實作程式碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
using namespace std;
class CircularQueue {
private:
int* queue;
int size;
int front;
int rear;
int tag;
public:
CircularQueue(int n) {
size = n;
queue = new int[size];
front = 0;
rear = 0;
tag = 0;
}
~CircularQueue() {
delete[] queue;
}
bool isFull() {
return (front == rear && tag == 1);
}
bool isEmpty() {
return (front == rear && tag == 0);
}
void enqueue(int value) {
if (isFull()) {
cout << "Queue is full" << endl;
return;
}
queue[rear] = value;
rear = (rear + 1) % size;
tag = 1;
}
int dequeue() {
if (isEmpty()) {
cout << "Queue is empty" << endl;
return -1;
}
int value = queue[front];
front = (front + 1) % size;
if (front == rear) {
tag = 0;
}
return value;
}
void display() {
if (isEmpty()) {
cout << "Queue is empty" << endl;
return;
}
int i = front;
cout << "Queue contents: ";
while (i != rear || (i == rear && tag == 1)) {
cout << queue[i] << " ";
i = (i + 1) % size;
if (i == front) break;
}
cout << endl;
}
};
int main() {
CircularQueue q(5);
q.enqueue(10);
q.enqueue(20);
q.enqueue(30);
q.enqueue(40);
q.enqueue(50);
q.display();
cout << "Dequeued: " << q.dequeue() << endl;
q.display();
q.enqueue(60);
q.display();
return 0;
}輸出內容
1
2
3
4Queue contents: 10 20 30 40 50
Dequeued: 10
Queue contents: 20 30 40 50 10
Queue contents: 20 30 40 50 60
Link List queue
實作程式碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
using namespace std;
class Node {
public:
int data;
Node* next;
Node(int val) {
data = val;
next = nullptr;
}
};
class Queue {
private:
Node* front;
Node* rear;
public:
Queue() {
front = rear = nullptr;
}
bool isEmpty() {
return front == nullptr;
}
void enqueue(int val) {
Node* newNode = new Node(val);
if (rear == nullptr) {
front = rear = newNode;
return;
}
rear->next = newNode;
rear = newNode;
}
int dequeue() {
if (isEmpty()) {
cout << "Queue is empty" << endl;
return -1;
}
Node* temp = front;
front = front->next;
if (front == nullptr) {
rear = nullptr;
}
int dequeuedData = temp->data;
delete temp;
return dequeuedData;
}
int peek() {
if (isEmpty()) {
cout << "Queue is empty" << endl;
return -1;
}
return front->data;
}
void display() {
if (isEmpty()) {
cout << "Queue is empty" << endl;
return;
}
Node* current = front;
while (current != nullptr) {
cout << current->data << " -> ";
current = current->next;
}
cout << "NULL" << endl;
}
};
int main() {
Queue q;
q.enqueue(10);
q.enqueue(20);
q.enqueue(30);
q.display();
cout << "Dequeue: " << q.dequeue() << endl;
q.display();
}