引言
你是否在超市排队结账时想过,这个简单的"排队"行为,恰恰体现了计算机科学中一个非常重要的数据结构------队列(Queue)?今天,将通过生动的例子和C++代码实现,全面掌握队列这一基础数据结构。
一、什么是队列?
队列是一种线性数据结构,遵循**先进先出(FIFO, First In First Out)**的原则。想象一下这些生活中的场景:
生活中的队列例子
🎬 电影院售票窗口
- 第一个来的人先买到票
- 后来的人在队伍末尾等待
- 没有人能插队(除非你想被群殴😄)
🍔 麦当劳点餐
- 点餐的顺序就是出餐的顺序
- 厨房按照订单先后制作食物
- 先点的人先拿到汉堡
🚗 单行道收费站
- 车辆依次通过
- 前面的车先通过,后面的车等待
- 不能倒车,不能超车
这些场景都完美诠释了队列的核心思想:先到先得。
二、队列的基本结构
出队方向 ← ← 入队方向
┌────────────────────────────────┐
│ Front Rear │
│ ↓ ↓ │
│ ┌───┐ ┌───┐ ┌───┐ ┌───┐ │
│ │ 1 │→ │ 2 │→ │ 3 │→ │ 4 │ │
│ └───┘ └───┘ └───┘ └───┘ │
└────────────────────────────────┘
关键术语:
- Front(队首):第一个元素的位置,出队的地方
- Rear(队尾):最后一个元素的位置,入队的地方
- Enqueue(入队):在队尾添加元素
- Dequeue(出队):从队首删除元素
三、队列的核心操作
1. 入队操作(Enqueue)
将新元素添加到队列的末尾。
cpp
初始状态: [1] → [2] → [3]
执行 enqueue(4)
结果: [1] → [2] → [3] → [4]
↑ ↑
Front Rear
2. 出队操作(Dequeue)
移除并返回队首元素。
cpp
初始状态: [1] → [2] → [3] → [4]
执行 dequeue() // 返回 1
结果: [2] → [3] → [4]
↑ ↑
Front Rear
3. 查看队首(Peek/Front)
查看但不删除队首元素。
cpp
初始状态: [5] → [6] → [7]
执行 peek() // 返回 5
结果: [5] → [6] → [7] // 队列不变
四、C++实现队列
方法一:使用数组实现(循环队列)
cpp
#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;
}
};
方法二:使用链表实现
cpp
#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(最简单)
cpp
#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;
}
五、完整示例:模拟银行排队系统
让我们用一个实际的例子来理解队列的应用:
cpp
#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. 操作系统进程调度
cpp
// 简化的进程调度模拟
struct Process {
int pid;
string name;
int priority;
};
queue<Process> readyQueue; // 就绪队列
// 进程按照到达顺序被CPU执行
2. 广度优先搜索(BFS)
cpp
// 图的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. 打印机任务队列
cpp
struct PrintJob {
string fileName;
int pages;
string user;
};
queue<PrintJob> printerQueue;
// 文档按照提交顺序打印
4. 消息队列系统
cpp
// 生产者-消费者模式
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)
优点:避免"假溢出",充分利用数组空间。
cpp
rearIdx = (rearIdx + 1) % capacity;
frontIdx = (frontIdx + 1) % capacity;
2. 双端队列(Deque)
两端都可以进行插入和删除操作。
cpp
deque<int> dq;
dq.push_front(1); // 队首插入
dq.push_back(2); // 队尾插入
dq.pop_front(); // 队首删除
dq.pop_back(); // 队尾删除
3. 优先队列(Priority Queue)
元素按优先级出队,而非先进先出。
cpp
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:用两个栈实现队列
cpp
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:实现循环队列
cpp
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)