Queue

队列概念

  • 定义:队列是一种数据结构,元素只能从一个端(后端)插入,从另一个端(前端)移除。
  • 特点:先进先出(FIFO, First-In, First-Out)
  • 基本操作
    • Enqueue:在队列的后端插入一个元素。
    • Dequeue:从队列的前端移除一个元素。
    • Front:获取队列前端元素但不移除它。
    • Length:返回队列中元素的数量。
    • Clear:清空队列。

队列抽象数据类型(ADT)定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template <typename E> 
class Queue {
private:
void operator =(const Queue&) {} // 保护赋值
Queue(const Queue&) {}; // 保护复制构造

public:
Queue() {}
virtual ~Queue() {}
virtual void clear() = 0; // 清空队列
virtual void enqueue(const E&) = 0; // 插入元素
virtual E dequeue() = 0; // 移除元素
virtual const E& frontValue() const = 0; // 获取前端元素
virtual int length() const = 0; // 返回队列长度
};

数组实现的队列

  • 类定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template <typename E> 
class AQueue : public Queue<E> {
private:
int maxSize; // 队列最大容量
int front; // 前端元素索引
int rear; // 后端元素索引
E *listArray; // 存储队列元素的数组
int currSize; // 队列当前长度
public:
// 构造函数和析构函数
AQueue(int size = defaultSize) { /*...*/ }
~AQueue() { delete[] listArray; }

// 其他成员函数...
};

循环队列

  • 特点
    • 队列的位置可以在数组内移动。
    • 前端最初位于数组的第0个位置,元素依次添加到更高的索引位置。
    • 移除元素时,前端索引增加。
    • Enqueue 和 Dequeue 操作的时间复杂度均为 $ (1) $。
  • 如何判断队列是否为空或满
    • front == rear 时,队列为空或满。
    • 解决方案:
      1. 显式保持队列中元素的计数。
      2. 数组大小设为 $ n+1 $,只允许存储 $ n $ 个元素:
        • front = rear + 1 时队列为空。
        • front = rear + 2 时队列为满。

数组实现(解决方案2)

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
template <typename E> 
class AQueue : public Queue<E> {
private:
int maxSize; // 最大队列容量
int front; // 前端元素索引
int rear; // 后端元素索引
E *listArray; // 存储队列元素的数组
public:
AQueue(int size = defaultSize) {
maxSize = size + 1; // 为空位置多留一个位置
rear = 0;
front = 1;
listArray = new E[maxSize];
}

~AQueue() { delete[] listArray; }

void clear() { rear = 0; front = 1; } // 重置队列
void enqueue(const E& it) {
Assert(((rear + 2) % maxSize) != front, "Queue is full");
rear = (rear + 1) % maxSize;
listArray[rear] = it;
}

E dequeue() {
Assert(length() != 0, "Queue is empty");
E it = listArray[front];
front = (front + 1) % maxSize;
return it;
}

const E& frontValue() const {
Assert(length() != 0, "Queue is empty");
return listArray[front];
}

virtual int length() const {
return ((rear + maxSize) - front + 1) % maxSize;
}
};

链表实现的队列

  • 结构
    • 使用头结点。
    • front 指针始终指向头结点。
    • rear 指针指向队列最后一个链节点。
  • 操作
    • Enqueue:在链表末尾插入新元素,并将 rear 指向新插入的节点。
    • Dequeue:移除并返回链表的第一个元素。

时间复杂度

  • 所有成员函数:对于两种实现,所有操作的时间复杂度均为 $ (1) $。

空间复杂度

  • 数组实现:如果队列未满,会有一些空间浪费。
  • 链表实现:每个元素都有链接域的开销。

数组实现的队列 (Array Implementation)

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
#include <iostream>
#include <cassert> // For assert

template <typename E>
class Queue {
public:
virtual ~Queue() {}
virtual void clear() = 0; // 清空队列
virtual void enqueue(const E&) = 0; // 入队
virtual E dequeue() = 0; // 出队
virtual const E& frontValue() const = 0; // 获取前端值
virtual int length() const = 0; // 获取队列长度
};

template <typename E>
class AQueue : public Queue<E> {
private:
int maxSize; // 队列的最大容量
int front; // 前端元素的索引
int rear; // 后端元素的索引
E* listArray; // 存放队列元素的数组

public:
AQueue(int size = 100) { // 默认大小为100
maxSize = size + 1; // 空出一个位置以便判断满队列
rear = 0;
front = 1;
listArray = new E[maxSize];
}

~AQueue() {
delete[] listArray; // 释放数组内存
}

// 清空队列
void clear() {
rear = 0;
front = 1;
}

// 入队操作
void enqueue(const E& it) {
assert((rear + 2) % maxSize != front && "队列已满");
rear = (rear + 1) % maxSize;
listArray[rear] = it;
}

// 出队操作
E dequeue() {
assert(length() != 0 && "队列为空");
E it = listArray[front];
front = (front + 1) % maxSize;
return it;
}

// 获取前端值
const E& frontValue() const {
assert(length() != 0 && "队列为空");
return listArray[front];
}

// 返回队列长度
virtual int length() const {
return (rear + maxSize - front + 1) % maxSize;
}
};

int main() {
AQueue<int> queue;
queue.enqueue(1);
queue.enqueue(2);
queue.enqueue(3);

std::cout << "Front Value: " << queue.frontValue() << std::endl; // 输出前端元素
std::cout << "Dequeued: " << queue.dequeue() << std::endl; // 出队
std::cout << "Front Value: " << queue.frontValue() << std::endl; // 输出新的前端元素

return 0;
}

链表实现的队列 (Linked List Implementation)

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
90
91
92
93
94
#include <iostream>
#include <cassert> // For assert

template <typename E>
class Node {
public:
E data; // 节点数据
Node* next; // 指向下一个节点的指针

Node(const E& data) : data(data), next(nullptr) {}
};

template <typename E>
class Queue {
public:
virtual ~Queue() {}
virtual void clear() = 0; // 清空队列
virtual void enqueue(const E&) = 0; // 入队
virtual E dequeue() = 0; // 出队
virtual const E& frontValue() const = 0; // 获取前端值
virtual int length() const = 0; // 获取队列长度
};

template <typename E>
class LQueue : public Queue<E> {
private:
Node<E>* front; // 指向队列前端的指针
Node<E>* rear; // 指向队列后端的指针
int currSize; // 队列当前长度

public:
LQueue() : front(nullptr), rear(nullptr), currSize(0) {}

~LQueue() {
clear();
}

// 清空队列
void clear() {
while (length() > 0) {
dequeue();
}
}

// 入队操作
void enqueue(const E& it) {
Node<E>* newNode = new Node<E>(it);
if (rear) {
rear->next = newNode;
} else {
front = newNode; // 如果队列为空,设置front
}
rear = newNode; // 更新后端指针
currSize++;
}

// 出队操作
E dequeue() {
assert(length() != 0 && "队列为空");
Node<E>* temp = front;
E it = front->data; // 获取前端数据
front = front->next; // 更新前端指针
delete temp; // 释放节点内存
currSize--;
if (length() == 0) {
rear = nullptr; // 如果队列为空,更新rear
}
return it;
}

// 获取前端值
const E& frontValue() const {
assert(length() != 0 && "队列为空");
return front->data;
}

// 返回队列长度
virtual int length() const {
return currSize;
}
};

int main() {
LQueue<int> queue;
queue.enqueue(1);
queue.enqueue(2);
queue.enqueue(3);

std::cout << "Front Value: " << queue.frontValue() << std::endl; // 输出前端元素
std::cout << "Dequeued: " << queue.dequeue() << std::endl; // 出队
std::cout << "Front Value: " << queue.frontValue() << std::endl; // 输出新的前端元素

return 0;
}

代码说明

  • 数组实现 (AQueue):
    • 使用数组存储队列元素。
    • 通过取模运算实现循环队列。
    • 提供了入队、出队、获取前端值及清空队列的方法。
  • 链表实现 (LQueue):
    • 使用链表节点实现队列。
    • 支持动态调整大小,避免了数组的固定大小限制。
    • 提供了相同的功能,包括入队、出队、获取前端值及清空队列。

这两个实现各有优缺点,数组实现具有较好的缓存性能,而链表实现则能灵活应对动态变化的队列大小。