深入理解队列(Queue):从原理到实践的完整指南
引言
你是否在超市排队结账时想过,这个简单的"排队"行为,恰恰体现了计算机科学中一个非常重要的数据结构——队列(Queue)?今天,将通过生动的例子和C++代码实现,全面掌握队列这一基础数据结构。
一、什么是队列?
队列是一种线性数据结构,遵循**先进先出(FIFO, First In First Out)**的原则。想象一下这些生活中的场景:
生活中的队列例子
🎬 电影院售票窗口
- 第一个来的人先买到票
- 后来的人在队伍末尾等待
- 没有人能插队(除非你想被群殴😄)
🍔 麦当劳点餐
- 点餐的顺序就是出餐的顺序
- 厨房按照订单先后制作食物
- 先点的人先拿到汉堡
🚗 单行道收费站
- 车辆依次通过
- 前面的车先通过,后面的车等待
- 不能倒车,不能超车
这些场景都完美诠释了队列的核心思想:先到先得。
二、队列的基本结构
出队方向 ← ← 入队方向┌────────────────────────────────┐│ Front Rear ││ ↓ ↓ ││ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ││ │ 1 │→ │ 2 │→ │ 3 │→ │ 4 │ ││ └───┘ └───┘ └───┘ └───┘ │└────────────────────────────────┘
关键术语:
- Front(队首):第一个元素的位置,出队的地方
- Rear(队尾):最后一个元素的位置,入队的地方
- Enqueue(入队):在队尾添加元素
- Dequeue(出队):从队首删除元素
三、队列的核心操作
1. 入队操作(Enqueue)
将新元素添加到队列的末尾。
初始状态: [1] → [2] → [3]
执行 enqueue(4)
结果: [1] → [2] → [3] → [4]↑ ↑Front Rear
2. 出队操作(Dequeue)
移除并返回队首元素。
初始状态: [1] → [2] → [3] → [4]
执行 dequeue() // 返回 1
结果: [2] → [3] → [4]↑ ↑Front Rear
3. 查看队首(Peek/Front)
查看但不删除队首元素。
初始状态: [5] → [6] → [7]
执行 peek() // 返回 5
结果: [5] → [6] → [7] // 队列不变
四、C++实现队列
方法一:使用数组实现(循环队列)
#include <iostream>
using namespace std;class Queue {
private:int* arr;int frontIdx;int rearIdx;int capacity;int count;public:// 构造函数Queue(int size) {arr = new int[size];capacity = size;frontIdx = 0;rearIdx = -1;count = 0;}// 析构函数~Queue() {delete[] arr;}// 入队操作void enqueue(int value) {if (isFull()) {cout << "队列已满!无法入队 " << value << endl;return;}rearIdx = (rearIdx + 1) % capacity; // 循环利用空间arr[rearIdx] = value;count++;cout << "✓ 入队成功: " << value << endl;}// 出队操作int dequeue() {if (isEmpty()) {cout << "队列为空!无法出队" << endl;return -1;}int value = arr[frontIdx];frontIdx = (frontIdx + 1) % capacity; // 循环移动count--;cout << "✓ 出队成功: " << value << endl;return value;}// 查看队首元素int peek() {if (isEmpty()) {cout << "队列为空!" << endl;return -1;}return arr[frontIdx];}// 检查队列是否为空bool isEmpty() {return count == 0;}// 检查队列是否已满bool isFull() {return count == capacity;}// 获取队列大小int size() {return count;}// 显示队列内容void display() {if (isEmpty()) {cout << "队列为空" << endl;return;}cout << "队列内容: ";int idx = frontIdx;for (int i = 0; i < count; i++) {cout << arr[idx] << " ";idx = (idx + 1) % capacity;}cout << endl;cout << "队首: " << arr[frontIdx] << ", 队尾: " << arr[rearIdx] << endl;}
};
方法二:使用链表实现
#include <iostream>
using namespace std;// 链表节点
struct Node {int data;Node* next;Node(int val) : data(val), next(nullptr) {}
};class LinkedQueue {
private:Node* frontPtr;Node* rearPtr;int count;public:LinkedQueue() {frontPtr = nullptr;rearPtr = nullptr;count = 0;}~LinkedQueue() {while (!isEmpty()) {dequeue();}}void enqueue(int value) {Node* newNode = new Node(value);if (isEmpty()) {frontPtr = rearPtr = newNode;} else {rearPtr->next = newNode;rearPtr = newNode;}count++;cout << "✓ 入队成功: " << value << endl;}int dequeue() {if (isEmpty()) {cout << "队列为空!无法出队" << endl;return -1;}Node* temp = frontPtr;int value = temp->data;frontPtr = frontPtr->next;if (frontPtr == nullptr) {rearPtr = nullptr;}delete temp;count--;cout << "✓ 出队成功: " << value << endl;return value;}int peek() {if (isEmpty()) {cout << "队列为空!" << endl;return -1;}return frontPtr->data;}bool isEmpty() {return frontPtr == nullptr;}int size() {return count;}void display() {if (isEmpty()) {cout << "队列为空" << endl;return;}cout << "队列内容: ";Node* current = frontPtr;while (current != nullptr) {cout << current->data << " → ";current = current->next;}cout << "NULL" << endl;}
};
方法三:使用STL(最简单)
#include <iostream>
#include <queue>
using namespace std;int main() {queue<int> q;// 入队q.push(10);q.push(20);q.push(30);cout << "队列大小: " << q.size() << endl;cout << "队首元素: " << q.front() << endl;cout << "队尾元素: " << q.back() << endl;// 出队q.pop();cout << "出队后队首: " << q.front() << endl;return 0;
}
五、完整示例:模拟银行排队系统
让我们用一个实际的例子来理解队列的应用:
#include <iostream>
#include <queue>
#include <string>
using namespace std;struct Customer {int id;string name;int serviceTime; // 所需服务时间(分钟)Customer(int i, string n, int t) : id(i), name(n), serviceTime(t) {}
};class BankQueue {
private:queue<Customer> customerQueue;int totalCustomers;int totalWaitTime;public:BankQueue() : totalCustomers(0), totalWaitTime(0) {}// 客户到达,加入队列void customerArrives(string name, int serviceTime) {totalCustomers++;Customer newCustomer(totalCustomers, name, serviceTime);customerQueue.push(newCustomer);cout << "📥 客户 " << name << " (编号: " << totalCustomers << ") 到达,需要服务时间: " << serviceTime << "分钟" << endl;cout << " 当前排队人数: " << customerQueue.size() << endl;cout << "-----------------------------------" << endl;}// 服务下一位客户void serveNextCustomer() {if (customerQueue.empty()) {cout << "❌ 没有客户在等待!" << endl;return;}Customer current = customerQueue.front();customerQueue.pop();cout << "✅ 正在服务客户: " << current.name << " (编号: " << current.id << ")" << endl;cout << " 服务时间: " << current.serviceTime << "分钟" << endl;cout << " 剩余排队人数: " << customerQueue.size() << endl;cout << "-----------------------------------" << endl;totalWaitTime += current.serviceTime;}// 查看下一位客户void peekNextCustomer() {if (customerQueue.empty()) {cout << "没有客户在等待" << endl;return;}Customer next = customerQueue.front();cout << "👀 下一位客户: " << next.name << " (编号: " << next.id << ")" << endl;}// 显示统计信息void showStatistics() {cout << "\n📊 银行统计信息" << endl;cout << "总服务客户数: " << totalCustomers << endl;cout << "当前排队人数: " << customerQueue.size() << endl;cout << "总服务时间: " << totalWaitTime << "分钟" << endl;if (totalCustomers > 0) {cout << "平均服务时间: " << (double)totalWaitTime / totalCustomers << "分钟" << endl;}}
};int main() {BankQueue bank;cout << "🏦 银行排队系统模拟开始" << endl;cout << "===================================\n" << endl;// 客户陆续到达bank.customerArrives("张三", 5);bank.customerArrives("李四", 3);bank.customerArrives("王五", 7);bank.customerArrives("赵六", 4);cout << "\n开始服务客户...\n" << endl;// 服务前两位客户bank.peekNextCustomer();bank.serveNextCustomer();bank.peekNextCustomer();bank.serveNextCustomer();// 又来了新客户cout << "\n新客户到达...\n" << endl;bank.customerArrives("钱七", 6);// 继续服务bank.serveNextCustomer();bank.serveNextCustomer();bank.serveNextCustomer();// 显示统计bank.showStatistics();return 0;
}
运行结果:
🏦 银行排队系统模拟开始
===================================📥 客户 张三 (编号: 1) 到达,需要服务时间: 5分钟当前排队人数: 1
-----------------------------------
📥 客户 李四 (编号: 2) 到达,需要服务时间: 3分钟当前排队人数: 2
-----------------------------------
📥 客户 王五 (编号: 3) 到达,需要服务时间: 7分钟当前排队人数: 3
-----------------------------------
📥 客户 赵六 (编号: 4) 到达,需要服务时间: 4分钟当前排队人数: 4
-----------------------------------开始服务客户...👀 下一位客户: 张三 (编号: 1)
✅ 正在服务客户: 张三 (编号: 1)服务时间: 5分钟剩余排队人数: 3
-----------------------------------
...
六、队列的实际应用场景
1. 操作系统进程调度
// 简化的进程调度模拟
struct Process {int pid;string name;int priority;
};queue<Process> readyQueue; // 就绪队列
// 进程按照到达顺序被CPU执行
2. 广度优先搜索(BFS)
// 图的BFS遍历
void BFS(Graph g, int start) {queue<int> q;bool visited[MAX_VERTICES] = {false};q.push(start);visited[start] = true;while (!q.empty()) {int current = q.front();q.pop();cout << current << " ";// 将所有未访问的邻接节点入队for (int neighbor : g.getNeighbors(current)) {if (!visited[neighbor]) {q.push(neighbor);visited[neighbor] = true;}}}
}
3. 打印机任务队列
struct PrintJob {string fileName;int pages;string user;
};queue<PrintJob> printerQueue;
// 文档按照提交顺序打印
4. 消息队列系统
// 生产者-消费者模式
queue<Message> messageQueue;// 生产者
void producer() {Message msg = createMessage();messageQueue.push(msg); // 发送消息
}// 消费者
void consumer() {if (!messageQueue.empty()) {Message msg = messageQueue.front();messageQueue.pop(); // 处理消息processMessage(msg);}
}
七、队列的变种
1. 循环队列(Circular Queue)
优点:避免"假溢出",充分利用数组空间。
rearIdx = (rearIdx + 1) % capacity;
frontIdx = (frontIdx + 1) % capacity;
2. 双端队列(Deque)
两端都可以进行插入和删除操作。
deque<int> dq;
dq.push_front(1); // 队首插入
dq.push_back(2); // 队尾插入
dq.pop_front(); // 队首删除
dq.pop_back(); // 队尾删除
3. 优先队列(Priority Queue)
元素按优先级出队,而非先进先出。
priority_queue<int> pq;
pq.push(30);
pq.push(10);
pq.push(20);
cout << pq.top(); // 输出 30(最大值)
八、性能分析
操作 | 时间复杂度 | 空间复杂度 |
---|---|---|
Enqueue | O(1) | O(1) |
Dequeue | O(1) | O(1) |
Peek | O(1) | O(1) |
Search | O(n) | O(1) |
空间占用 | - | O(n) |
数组 vs 链表实现对比:
特性 | 数组实现 | 链表实现 |
---|---|---|
内存分配 | 连续 | 分散 |
大小 | 固定 | 动态 |
缓存友好性 | 好 | 较差 |
内存开销 | 低 | 高(需要指针) |
九、常见面试题
题目1:用两个栈实现队列
class QueueUsingStacks {
private:stack<int> s1, s2;public:void enqueue(int x) {s1.push(x);}int dequeue() {if (s2.empty()) {while (!s1.empty()) {s2.push(s1.top());s1.pop();}}int val = s2.top();s2.pop();return val;}
};
题目2:实现循环队列
class MyCircularQueue {
private:vector<int> data;int front, rear, size, capacity;public:MyCircularQueue(int k) {data.resize(k);front = rear = size = 0;capacity = k;}bool enQueue(int value) {if (isFull()) return false;data[rear] = value;rear = (rear + 1) % capacity;size++;return true;}bool deQueue() {if (isEmpty()) return false;front = (front + 1) % capacity;size--;return true;}bool isFull() { return size == capacity; }bool isEmpty() { return size == 0; }
};
十、总结
队列是一种简单但强大的数据结构,其"先进先出"的特性使其在计算机科学的各个领域都有广泛应用。通过本文,我们学习了:
✅ 队列的基本概念和生活中的类比
✅ 三种C++实现方式:数组、链表、STL
✅ 实际应用案例:银行排队系统
✅ 队列的变种和高级应用
✅ 性能分析和面试常见题目
掌握队列不仅能帮助你解决实际编程问题,更能加深对算法和数据结构的理解。记住:队列就像生活中的排队,先到先得,公平合理!
练习题
- 实现一个队列,支持获取队列中的最大值(要求时间复杂度O(1))
- 设计一个击鼓传花游戏的模拟程序
- 使用队列实现二叉树的层序遍历
- 实现一个滑动窗口的最大值算法
💡 建议:动手实现本文中的代码,运行看看效果。理论结合实践,才能真正掌握队列!
📚 下一步学习:栈(Stack)、链表(Linked List)、哈希表(Hash Table)