- C++
C++ 队列(queue)
- 2025-5-22 22:17:04 @
🧱 C++ 队列(queue)通俗易懂教程 🚀
🎯 一、什么是队列?
在编程中,队列(Queue) 是一种非常常用的数据结构,它的特点是:
先进先出(FIFO, First In First Out)
就像你在超市排队结账一样:谁先来谁就先走!
📦 二、C++ 中的 queue 简介
在 C++ 标准库中,<queue>
头文件提供了一个现成的 queue
容器适配器。
📌 特点总结:
- 只能从队尾入队(push)
- 只能从队头出队(pop)
- 不支持随机访问
- 默认使用
deque
作为底层容器
🔧 三、基本操作一览表 ⚙️
操作 | 功能说明 |
---|---|
q.push(x) |
把元素 x 插入到队列末尾 |
q.pop() |
移除队列的第一个元素 |
q.front() |
返回队列第一个元素(不删除) |
q.back() |
返回队列最后一个元素(不删除) |
q.empty() |
判断队列是否为空 |
q.size() |
返回队列中元素的数量 |
🧪 四、代码演示 ✅
#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; // 输出:3
cout << "队头元素: " << q.front() << endl; // 输出:10
cout << "队尾元素: " << q.back() << endl; // 输出:30
// 删除队头元素
q.pop();
cout << "新的队头元素: " << q.front() << endl; // 输出:20
// 判断是否为空
if (q.empty()) {
cout << "队列为空!";
} else {
cout << "队列非空!";
}
return 0;
}
🧠 五、举个生活中的例子 💡
想象你正在食堂排队打饭:
🧑🍳 前面的同学一个一个打完饭离开了(pop
),
👨🎓 后面的同学一个个加入队伍(push
),
👀 你可以看到排在最前面的人是谁(front
),
🔚 也可以看到最后一个刚来的同学是谁(back
),
📏 还可以知道现在有多少人排队(size
)。
这就是一个标准的 队列模型!
🌈 六、应用场景举例 📚
应用场景 | 说明 |
---|---|
BFS 广度优先搜索 | 使用队列来管理待访问节点 |
打印任务队列 | 多个打印请求按顺序处理 |
线程池任务调度 | 新任务加入队列等待线程执行 |
键盘输入缓冲区 | 用户按键顺序保存并依次处理 |
📌 七、注意事项 ❗
- 不能直接访问中间元素,只能通过 front 和 back 访问两端。
- 如果队列为空时调用
front()
或pop()
,行为是未定义的,可能导致崩溃!记得检查是否为空哦!
if (!q.empty()) {
cout << q.front() << endl;
}
🎨 八、可视化图解 📊
初始状态:
[ ] ← 队头 队尾 → [ ]
插入 10:
[10] ← 队头 队尾 → [10]
插入 20:
[10] ← 队头 队尾 → [20]
插入 30:
[10] ← 队头 队尾 → [30]
弹出一次 pop():
[20] ← 队头 队尾 → [30]
🧩 九、常见错误与解决方法 🛠️
错误类型 | 原因 | 解决方案 |
---|---|---|
编译错误 | 忘记包含头文件 <queue> |
添加 #include <queue> |
运行错误 | 对空队列调用 front() 或 pop() |
使用前加 if (!q.empty()) 判断 |
逻辑错误 | 误以为可以访问中间元素 | 队列只允许访问首尾 |
🎁 十、小彩蛋:自定义类型的队列 🎲
不仅可以放 int
,还可以放结构体、类等自定义类型!
struct Student {
string name;
int age;
};
queue<Student> students;
students.push({"小明", 15});
students.push({"小红", 16});
cout << students.front().name << endl; // 输出:小明
🏆 十一、加油鼓励语 💪
👋 亲爱的同学,你已经掌握了 C++ 的
queue
容器,这是通往算法和数据结构大门的重要一步!🎯 队列虽然简单,但它是 BFS、任务调度、缓冲机制等高级应用的基础。
🚀 继续加油,坚持学习,你一定会成为编程高手!我们在这里为你点赞!👏👏👏
📝 十二、参考资料 & 推荐学习 🔍
资源名称 | 地址 |
---|---|
C++ Reference - queue | https://cplusplus.com/reference/queue/queue/ |
LeetCode 队列题目合集 | https://leetcode.cn/tag/queue/ |
Bilibili C++ STL 教程 | https://www.bilibili.com/video/xxx |
📅 最后更新时间:2025年5月22日 22:14
🎉 祝你学得开心,编程顺利,早日成为大神!🌟
2 条评论
-
admin SU @ 2025-5-22 22:22:00
自己实现一个数组+结构体版队列
队列是一种基础数据结构,遵循"先进先出(FIFO)"原则。下面我将通过结构体和数组实现一个完整的队列,包含所有核心操作,并添加详细注释和使用示例。
队列的结构体设计
首先我们需要设计一个队列结构体,包含数据存储数组、头尾指针和辅助变量:
#include <iostream> using namespace std; // 队列最大容量 const int MAX_QUEUE_SIZE = 100; // 队列结构体定义 typedef struct { int data[MAX_QUEUE_SIZE]; // 存储队列元素的数组 int front; // 队头指针,指向队头元素 int rear; // 队尾指针,指向队尾元素的下一个位置 int size; // 队列中元素的数量 } Queue; // 初始化队列 void initQueue(Queue* q) { q->front = 0; q->rear = 0; q->size = 0; } // 检查队列是否为空 bool isEmpty(Queue* q) { return q->size == 0; } // 检查队列是否已满 bool isFull(Queue* q) { return q->size == MAX_QUEUE_SIZE; } // 入队操作:将元素添加到队尾 bool enqueue(Queue* q, int item) { if (isFull(q)) { cout << "队列已满,无法入队!" << endl; return false; } // 使用循环队列方式处理队尾指针 q->data[q->rear] = item; q->rear = (q->rear + 1) % MAX_QUEUE_SIZE; q->size++; return true; } // 出队操作:从队头移除元素并返回 int dequeue(Queue* q) { if (isEmpty(q)) { cout << "队列为空,无法出队!" << endl; return -1; // 返回-1表示错误 } int item = q->data[q->front]; q->front = (q->front + 1) % MAX_QUEUE_SIZE; q->size--; return item; } // 获取队头元素但不出队 int getFront(Queue* q) { if (isEmpty(q)) { cout << "队列为空,无法获取队头元素!" << endl; return -1; } return q->data[q->front]; } // 获取队列中元素个数 int getSize(Queue* q) { return q->size; } // 打印队列所有元素 void printQueue(Queue* q) { if (isEmpty(q)) { cout << "队列为空" << endl; return; } cout << "队列中的元素(从队头到队尾):"; int i = q->front; while (i != q->rear) { cout << q->data[i] << " "; i = (i + 1) % MAX_QUEUE_SIZE; } cout << endl; } // 清空队列 void clearQueue(Queue* q) { q->front = 0; q->rear = 0; q->size = 0; }
循环队列的工作原理
上面的实现使用了循环队列技术,这是为什么呢?
假设我们有一个固定大小的数组,如果不使用循环队列,会出现"假溢出"问题:
- 初始状态:
front=0, rear=0, size=0
- 入队10:
data[0]=10, rear=1, size=1
- 入队20:
data[1]=20, rear=2, size=2
- 出队:
front=1, size=1
,此时数组索引0位置为空 - 再次入队时,如果直接使用
rear++
,会导致数组末尾被填满,但前面还有空位置
循环队列通过取模运算解决了这个问题:
q->rear = (q->rear + 1) % MAX_QUEUE_SIZE; // 入队时 q->front = (q->front + 1) % MAX_QUEUE_SIZE; // 出队时
这样当指针到达数组末尾时,会自动回到数组开头,形成一个环形结构。
完整的使用示例
下面是一个完整的示例,展示如何使用我们实现的队列:
int main() { // 创建队列结构体变量 Queue myQueue; // 初始化队列 initQueue(&myQueue); cout << "=== 队列操作演示 ===" << endl; // 测试入队操作 cout << "入队操作:" << endl; enqueue(&myQueue, 10); enqueue(&myQueue, 20); enqueue(&myQueue, 30); printQueue(&myQueue); // 应该输出: 10 20 30 // 测试获取队列大小 cout << "队列大小:" << getSize(&myQueue) << endl; // 应该输出: 3 // 测试获取队头元素 cout << "队头元素:" << getFront(&myQueue) << endl; // 应该输出: 10 // 测试出队操作 cout << "出队元素:" << dequeue(&myQueue) << endl; // 应该输出: 10 printQueue(&myQueue); // 应该输出: 20 30 // 测试再次入队 cout << "入队操作:" << endl; enqueue(&myQueue, 40); printQueue(&myQueue); // 应该输出: 20 30 40 // 测试清空队列 cout << "清空队列..." << endl; clearQueue(&myQueue); cout << "清空后队列是否为空:" << (isEmpty(&myQueue) ? "是" : "否") << endl; // 应该输出: 是 return 0; }
队列操作的时间复杂度
我们实现的队列各项操作的时间复杂度如下:
操作 时间复杂度 说明 初始化队列 O(1) 仅设置指针和变量 入队(enqueue) 直接操作队尾指针 出队(dequeue) 直接操作队头指针 获取队头元素 直接访问队头元素 获取队列大小 直接返回size变量 打印队列 O(n) 需要遍历所有元素 清空队列 O(1) 仅重置指针和变量 队列与其他数据结构的区别
-
队列 vs 栈:
- 队列:先进先出(FIFO),元素从队尾入队,队头出队
- 栈:后进先出(LIFO),元素从栈顶入栈,栈顶出栈
-
队列 vs 数组/链表:
- 队列是一种抽象数据类型,定义了特定的操作规则
- 数组/链表是物理存储结构,可以用来实现队列
拓展:实现一个通用型队列
如果希望队列可以存储不同类型的数据,可以使用C++模板:
template <typename T> typedef struct { T data[MAX_QUEUE_SIZE]; int front; int rear; int size; } GenericQueue; // 模板函数实现...(与上面类似,将int改为T)
总结
通过结构体和数组实现队列,我们深入理解了队列的内部工作原理。这种实现方式:
- 空间效率高,利用固定大小数组存储数据
- 时间效率高,核心操作均为O(1)复杂度
- 通过循环队列解决了"假溢出"问题
掌握队列的实现和原理,对理解更复杂的数据结构和算法(如广度优先搜索、任务调度等)非常有帮助。继续加油,你正在编程之路上稳步前进!
- 初始状态:
-
2025-5-22 22:20:05@
C++队列(Queue)完全教程:从入门到实战
🚀 欢迎来到队列的世界
在编程的世界里,数据结构就像是我们存放和管理数据的"工具箱"。今天我们要认识的这个"工具"叫做队列(Queue),它是一种非常基础但又极其重要的数据结构。学会队列,你就掌握了处理"先来先服务"类问题的关键技能!
🌈 什么是队列?
队列是一种线性数据结构,它的特点是先进先出(First In First Out, FIFO)。就像我们日常生活中排队买票一样,先来的人先买到票离开,后来的人后买到票离开。
🎨 队列的形象比喻
想象一个水管:
┌───────────────┐ │ 数据元素1 │ <- 队头(Front) ├───────────────┤ │ 数据元素2 │ ├───────────────┤ │ 数据元素3 │ └───────────────┘ <- 队尾(Rear)
- 新元素只能从队尾进入队列(入队操作)
- 元素只能从队头离开队列(出队操作)
📦 C++中的队列实现
在C++中,我们可以通过两种方式使用队列:
- 使用STL标准库中的
queue
容器适配器 - 自己动手实现一个队列类
🌟 方法一:使用STL的queue
STL(Standard Template Library)为我们提供了便捷的
queue
容器,我们可以直接使用:#include <iostream> #include <queue> // 队列头文件 using namespace std; int main() { // 创建一个整数队列 queue<int> myQueue; // 1. 入队操作(push):将元素添加到队尾 myQueue.push(10); myQueue.push(20); myQueue.push(30); cout << "入队后队列中的元素:"; // 注意:queue不支持直接遍历,我们需要借助临时队列查看 queue<int> tempQueue = myQueue; while (!tempQueue.empty()) { cout << tempQueue.front() << " "; // 2. 访问队头元素(front) tempQueue.pop(); // 3. 出队操作(pop):移除队头元素 } cout << endl; // 4. 查看队列大小(size) cout << "队列中元素的个数:" << myQueue.size() << endl; // 5. 检查队列是否为空(empty) if (myQueue.empty()) { cout << "队列为空" << endl; } else { cout << "队列不为空" << endl; } // 6. 访问队尾元素(注意:STL queue没有直接的back(),需要自己实现) tempQueue = myQueue; int lastElement = 0; while (!tempQueue.empty()) { lastElement = tempQueue.front(); tempQueue.pop(); } cout << "队尾元素是:" << lastElement << endl; return 0; }
🛠️ STL队列的常用操作
操作 描述 时间复杂度 push(element)
将元素添加到队尾 O(1) pop()
移除队头元素 front()
访问队头元素 size()
返回队列中元素的个数 empty()
检查队列是否为空 🔨 方法二:自己实现一个队列(数组版)
为了更好地理解队列的内部原理,我们可以自己动手实现一个基于数组的队列:
#include <iostream> using namespace std; const int MAX_SIZE = 100; // 队列的最大容量 class Queue { private: int arr[MAX_SIZE]; // 存储队列元素的数组 int front; // 队头指针 int rear; // 队尾指针 int count; // 队列中元素的个数 public: // 构造函数 Queue() { front = 0; rear = -1; count = 0; } // 入队操作 bool enqueue(int item) { if (isFull()) { cout << "队列已满,无法入队!" << endl; return false; } rear = (rear + 1) % MAX_SIZE; // 循环队列的处理 arr[rear] = item; count++; return true; } // 出队操作 int dequeue() { if (isEmpty()) { cout << "队列为空,无法出队!" << endl; return -1; // 假设-1表示错误 } int item = arr[front]; front = (front + 1) % MAX_SIZE; // 循环队列的处理 count--; return item; } // 查看队头元素 int peek() { if (isEmpty()) { cout << "队列为空,无法查看!" << endl; return -1; } return arr[front]; } // 查看队列大小 int size() { return count; } // 检查队列是否为空 bool isEmpty() { return count == 0; } // 检查队列是否已满(针对循环队列) bool isFull() { return count == MAX_SIZE; } // 打印队列中的元素 void printQueue() { if (isEmpty()) { cout << "队列为空" << endl; return; } cout << "队列中的元素:"; int i = front; while (i != rear) { cout << arr[i] << " "; i = (i + 1) % MAX_SIZE; } cout << arr[rear] << endl; } }; int main() { Queue myQueue; cout << "=== 测试队列操作 ===" << endl; // 测试入队 myQueue.enqueue(10); myQueue.enqueue(20); myQueue.enqueue(30); myQueue.printQueue(); // 应该输出: 10 20 30 // 测试查看队头 cout << "队头元素:" << myQueue.peek() << endl; // 应该输出: 10 // 测试出队 cout << "出队元素:" << myQueue.dequeue() << endl; // 应该输出: 10 myQueue.printQueue(); // 应该输出: 20 30 // 测试队列大小 cout << "队列大小:" << myQueue.size() << endl; // 应该输出: 2 return 0; }
🔄 循环队列 vs 普通队列
上面的数组实现中,我们使用了"循环队列"的技巧,这是为什么呢?
🚦 普通队列的问题
如果我们不使用循环队列,当多次入队和出队后,会出现"假溢出"现象:
初始状态: [0,0,0,0,0] front=0, rear=-1 入队10: [10,0,0,0,0] front=0, rear=0 入队20: [10,20,0,0,0] front=0, rear=1 出队: [0,20,0,0,0] front=1, rear=1 入队30: [0,20,30,0,0] front=1, rear=2 出队: [0,0,30,0,0] front=2, rear=2
此时虽然数组还有空间,但
rear
已经到达数组末尾,无法继续入队,这就是"假溢出"。🔄 循环队列的解决方案
循环队列将数组视为一个环,当
rear
到达数组末尾时,下一个位置回到数组开头:rear = (rear + 1) % MAX_SIZE; // 入队时 front = (front + 1) % MAX_SIZE; // 出队时
这样就解决了"假溢出"问题,充分利用了数组空间。
📖 队列的应用场景
队列在实际编程中有非常广泛的应用:
-
操作系统中的任务调度:操作系统使用队列来管理待执行的任务,按照"先来先服务"的原则处理任务。
-
打印机任务队列:多个打印任务进入队列,按顺序打印。
-
广度优先搜索(BFS):在图论算法中,BFS算法使用队列来遍历图的节点。
-
网络请求处理:服务器处理客户端的请求时,通常使用队列来管理请求。
-
消息队列系统:在分布式系统中,消息队列用于解耦服务之间的通信。
🌈 实战练习:用队列解决问题
📝 问题:模拟打印机队列
问题描述: 有n个打印任务进入打印机队列,每个任务有一个优先级(1-10),10为最高优先级。打印机每次从队列中取出一个任务:
- 如果队列中存在优先级更高的任务,则当前任务重新进入队列尾部
- 否则,打印当前任务
请计算每个任务的实际打印顺序。
#include <iostream> #include <queue> #include <vector> using namespace std; int main() { int n; // 任务数量 cout << "请输入任务数量:"; cin >> n; queue<pair<int, int>> taskQueue; // 任务队列,存储(任务ID, 优先级) vector<int> priorities(n); // 存储每个任务的优先级 // 输入每个任务的优先级 cout << "请输入每个任务的优先级(1-10):" << endl; for (int i = 0; i < n; ++i) { cin >> priorities[i]; taskQueue.push({i + 1, priorities[i]}); // 任务ID从1开始 } int printOrder = 0; // 打印顺序 vector<int> result(n); // 存储每个任务的打印顺序 while (!taskQueue.empty()) { auto currentTask = taskQueue.front(); taskQueue.pop(); // 检查队列中是否有更高优先级的任务 bool higherPriorityExists = false; for (auto task : taskQueue) { if (task.second > currentTask.second) { higherPriorityExists = true; break; } } if (higherPriorityExists) { // 有更高优先级的任务,当前任务重新入队 taskQueue.push(currentTask); } else { // 没有更高优先级的任务,打印当前任务 printOrder++; result[currentTask.first - 1] = printOrder; cout << "任务 " << currentTask.first << " 正在打印,打印顺序:" << printOrder << endl; } } // 输出每个任务的打印顺序 cout << "\n每个任务的打印顺序:" << endl; for (int i = 0; i < n; ++i) { cout << "任务 " << (i + 1) << ": " << result[i] << endl; } return 0; }
🧩 问题分析
这个问题很好地体现了队列的"先进先出"特性,同时结合了优先级的概念。程序的核心逻辑是:
- 使用队列存储等待打印的任务
- 每次取出队头任务,检查是否有更高优先级的任务
- 如果有,当前任务重新入队
- 否则,打印当前任务并记录顺序
💪 挑战自我:实现双端队列(Deque)
双端队列(Deque)是一种特殊的队列,它允许在队头和队尾都进行插入和删除操作。试着自己实现一个双端队列吧!
提示:双端队列的实现需要考虑:
- 在队头和队尾都能进行插入和删除
- 处理边界情况(空队列、满队列)
- 可以使用数组或链表实现
🌟 总结与加油
恭喜你完成了队列的学习!现在你已经掌握了:
- 队列的基本概念和"先进先出"特性
- C++中使用STL队列和自己实现队列的方法
- 循环队列的原理和实现
- 队列的实际应用场景
- 用队列解决实际问题
编程学习就像队列一样,每一步的积累都会按顺序发挥作用。不要害怕遇到困难,每一次挑战都是成长的机会!继续加油,下一个数据结构在等着你呢~
📚 拓展学习
- 了解更多STL容器:
deque
(双端队列)、priority_queue
(优先队列) - 学习队列在图算法中的应用:广度优先搜索(BFS)
- 研究队列在操作系统中的具体实现
- 尝试用队列解决更多实际问题,比如银行排队系统模拟
相信自己,你一定可以在编程的道路上走得更远!🚀
- 1