跳到主要内容
队列(Queue)数据结构详解及例题 | 极客日志
C++ 算法
队列(Queue)数据结构详解及例题 四种队列数据结构:普通队列、循环队列、优先队列和双端队列。涵盖定义、基本操作、实现方式(结构体/数组/STL)及典型例题。包括机器翻译、班级值日表、数列极差及滑动窗口问题。重点讲解 FIFO 特性、循环处理机制、堆排序原理及单调队列优化。适合学习数据结构基础及算法竞赛入门。
片刻 发布于 2026/3/30 更新于 2026/5/25 33 浏览简介
队列是一种**先进先出(FIFO, First In First Out)**的线性数据结构,类似于生活中排队等候的场景——先来的人先接受服务,后来的人只能排在队尾等待。这种直观的规则决定了队列的核心特性:数据存取具有严格的时间顺序性 。
从结构上看,队列是一种操作受限的线性表,它允许在表的一端(称为队尾 )进行插入操作,在另一端(称为队头 )进行删除操作。这意味着元素进入队列时只能从队尾加入,离开队列时只能从队头移除,任何不在队头的元素都无法被直接访问,必须等到它前面的所有元素都出队后,才能成为新的队头。这种'先进先出'的机制,使得队列天然适合处理需要按顺序处理的场景。
队列的实现方式灵活多样,既可以使用数组 (静态队列,通常实现为循环队列以提高空间利用率),也可以使用链表 (动态队列)来构建。数组实现简单高效,访问速度快,但需要预先分配固定大小的空间,通过循环队列可以避免'假溢出'问题;链表实现则可以动态扩展,按需分配内存,更加灵活,但每个节点需要额外的指针存储空间。
队列的应用遍布计算机科学的各个领域。在操作系统层面,任务调度队列 用于管理进程和线程的执行顺序,IO 请求队列 用于缓冲和管理输入输出请求;在网络通信中,数据包队列 用于缓冲网络数据包,处理拥塞控制;在程序设计层面,消息队列 用于进程间通信和解耦系统组件,缓冲队列 用于平衡生产者和消费者的速度差异。在算法设计中,队列是广度优先搜索(BFS)的核心数据结构,广泛应用于最短路径查找、树的层序遍历等场景。此外,日常应用中的 打印任务队列 、键盘输入缓冲区 等,底层都依赖队列来实现。
队列的优缺点同样鲜明。它的优点是实现简单、操作高效 (所有基本操作均为 O(1) 时间复杂度),并且能够自然地处理具有先进先出逻辑的问题。它的缺点是访问受限 ——无法直接访问或修改队列中任意位置的元素,必须遵循严格的存取顺序。
总的来说,队列是一种简单而强大的数据结构,它以受限的访问方式换取了清晰的逻辑结构和高效的运行性能,是计算机科学中不可或缺的基础构件,在系统设计、算法实现和日常应用中发挥着重要作用。
一、普通队列
1. 普通队列的定义
队列的定义有多种格式,如下:
queue<int > q;
deque<int > d = {1 , 2 , 3 , 4 , 5 };
queue<int > q (d) ;
list<int > l;
queue<int , list<int >> q (l);
2. 普通队列的基本操作
q.push(x)
3. 手打队列
(1)使用结构体 const int MAXN = 1e6 + 5 ;
struct Queue {
int q[MAXN], head, tail;
Queue () {
memset (q, 0 , sizeof (q));
head = tail = 0 ;
}
void push (int x) {
q[++tail] = x;
}
void pop () {
head++;
}
int front () {
return q[head + 1 ];
}
int back () {
return q[tail];
}
int size () {
return tail - head;
}
bool empty () {
return head == tail;
};
};
(2)使用数组
4. 例题 洛谷 P1540 [NOIP 2010 提高组] 机器翻译
题目大意 给你 n 个数,如果 a_i 没在内存中出现过且内存数量小于 m,将 a_i 加入内存,否则弹出内存最后一项,将 a_i 加入内存。
思路 使用队列来维护内存,加入内存就是 push(),弹出就是 pop()。
Code #include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
const int MAXN = 1e3 + 5 ;
int m, n, a[MAXN], cnt;
bool vis[MAXN];
queue<int > q;
int main () {
cin >> m >> n;
for (int i = 1 ; i <= n; i++) {
cin >> a[i];
if (!vis[a[i]]) {
if (q.size () < m) {
q.push (a[i]);
vis[a[i]] = true ;
} else {
vis[q.front ()] = false ;
q.pop ();
q.push (a[i]);
vis[a[i]] = true ;
}
cnt++;
}
}
cout << cnt << endl;
return 0 ;
}
二、循环队列
1. 手打循环队列 循环队列没有特定的头文件,通常需要手打,下面给出一个范例
const int MAXN = 1e6 + 5 ;
struct CircularQueue {
int q[MAXN], head, tail, cnt;
CircularQueue () {
memset (q, 0 , sizeof (q));
head = tail = cnt = 0 ;
}
void push (int x) {
if (cnt == MAXN) {
return ;
}
q[tail] = x;
tail = (tail + 1 ) % MAXN;
cnt++;
}
void pop () {
head = (head + 1 ) % MAXN;
cnt--;
}
int front () {
return q[head];
}
int back () {
return q[(tail - 1 + MAXN) % MAXN];
}
int size () {
return cnt;
}
bool empty () {
return !cnt;
}
};
2. 循环队列的基本操作 操作 说明 q.push(x)将 x 入队(如果满了就无法入队) q.pop()删除 q 的队首元素 q.front()返回 q 的队首元素 q.back()返回 q 的队尾元素 q.size()返回 q 的元素数量 q.empty()返回 q 是否为空
3. 例题
班级循环值日表
题目描述 班主任为了培养同学们的责任感,制定了一个循环值日制度 。班级有 n 个同学,编号为 1 到 n。每个工作日需要安排 m 个同学值日。
这 m 个同学值日后,按照他们当天值日的顺序 ,重新加入到队伍的队尾 (保持他们的相对顺序)
如此循环往复
第二天继续从新的队首开始选 m 个同学
每天从当前值日队伍的队首 开始,连续选出 m 个同学担任当天的值日生
班主任想知道,前 k 天 中,每天的值日生编号 以及经过 k 天后,队伍中同学的顺序 。
输入格式 一行三个整数 n, m, k,表示:
k:天数
m:每天值日人数
n:班级总人数
输出格式 输出共 k+1 行:
第 k+1 行:n 个整数,表示经过 k 天后,队伍中同学的顺序(从队首到队尾)
前 k 行:每行 m 个整数,表示当天值日的同学编号(按值日顺序)
输入输出样例
输入 #1
输出 #1
样例解释
初始队伍:1 2 3 4 5
第 1 天:值日生 1 2 → 他们值日后到队尾,队伍变为:3 4 5 1 2
第 2 天:值日生 3 4 → 队伍变为:5 1 2 3 4
第 3 天:值日生 5 1 → 队伍变为:3 4 2 5 1(注意:值日生 5 和 1 保持顺序到队尾)
第 4 行输出第 3 天结束后的队伍顺序:3 4 2 5 1
输入 #2
输出 #2
样例解释:
初始:1 2 3 4
第 1 天:值日 1 2 3 → 队伍:4 1 2 3
第 2 天:值日 4 1 2 → 队伍:3 4 1 2
数据范围 对于 100% 的数据:1 ≤ n ≤ 1000,1 ≤ m ≤ n,1 ≤ k ≤ 1000
对于 30% 的数据:1 ≤ n, k ≤ 100
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
const int MAXN = 1e3 + 5 ;
struct Queue {
int q[MAXN];
int head, tail, cnt;
Queue () {
head = 1 ;
tail = 1 ;
cnt = 0 ;
}
void push (int x) {
if (cnt == MAXN) return ;
q[tail] = x;
tail++;
if (tail > MAXN) tail = 1 ;
cnt++;
}
void pop () {
if (cnt == 0 ) return ;
head++;
if (head > MAXN) head = 1 ;
cnt--;
}
int front () {
return q[head];
}
int size () {
return cnt;
}
bool empty () {
return cnt == 0 ;
}
int get (int i) {
int pos = head + i - 1 ;
if (pos > MAXN) pos -= MAXN;
return q[pos];
}
};
int main () {
int n, m, k;
cin >> n >> m >> k;
Queue q;
for (int i = 1 ; i <= n; i++) {
q.push (i);
}
for (int i = 1 ; i <= k; i++) {
int duty[MAXN];
for (int j = 1 ; j <= m; j++) {
duty[j] = q.front ();
q.pop ();
}
for (int j = 1 ; j <= m; j++) {
cout << duty[j];
if (j < m) cout << " " ;
}
cout << endl;
for (int j = 1 ; j <= m; j++) {
q.push (duty[j]);
}
}
for (int i = 1 ; i <= q.size (); i++) {
cout << q.get (i);
if (i < q.size ()) cout << " " ;
}
cout << endl;
return 0 ;
}
三、优先队列 优先队列的底层是二叉堆,有兴趣可以参考(十分建议)
1. 优先队列的定义 priority_queue<int > q;
priority_queue<int , vector<int >, greater<int >> q;
struct cmp {
bool operator () (int a, int b) {
return a > b;
}
};
priority_queue<int , vector<int >, cmp> q;
2. 优先队列的性质 如果是大根堆,那么在输出时会是从大到小,否则会是从小到大。
是的,优先队列在插入新元素时会自动排序!
而且仅需 O(log(n))!这样排序就方便多了
3. 优先队列的基本函数
4. 例题
数列极差
题目描述 佳佳的老师在黑板上写了一个由 n 个正整数组成的数列,要求佳佳进行如下操作:每次擦去其中的两个数 a 和 b,然后在数列中加入一个数 a × b + 1,如此下去直至黑板上剩下一个数为止,在所有按这种操作方式最后得到的数中,最大的为 max,最小的为 min,则该数列的极差定义为 M = max - min。
由于佳佳忙于准备期末考试,现请你帮助他,对于给定的数列,计算出相应的极差 M。
输入格式 第一行为一个正整数 n 表示正整数序列的长度;
在接下来的 n 行中,每行输入一个正整数。
接下来的一行有一个 0,表示数据结束。
输出格式
输入 #1
输出 #1
说明提示 对于全部数据,0 ≤ n ≤ 5 × 10^5,保证所有数据计算均在 32 位有符号整数范围内。
Code #include <iostream>
#include <algorithm>
#include <queue>
#include <vector>
#include <climits>
using namespace std;
priority_queue<int , vector<int >, greater<int >> maxn;
priority_queue<int , vector<int >, less<int >> minn;
int n, k;
int main () {
cin >> n;
for (int i = 1 ; i <= n; i++) {
cin >> k;
maxn.push (k);
minn.push (k);
}
cin >> k;
while (maxn.size () > 1 ) {
int a = maxn.top ();
maxn.pop ();
int b = maxn.top ();
maxn.pop ();
maxn.push (a * b + 1 );
}
while (minn.size () > 1 ) {
int a = minn.top ();
minn.pop ();
int b = minn.top ();
minn.pop ();
minn.push (a * b + 1 );
}
cout << maxn.top () - minn.top () << endl;
return 0 ;
}
四、双端队列与滑动窗口
1. 双端队列的定义 deque<int > q;
deque<int > q (10 ) ;
deque<int > q (10 , 5 ) ;
deque<int > q = {1 , 2 , 3 , 4 , 5 };
deque<int > q{1 , 2 , 3 , 4 , 5 };
int a[] = {1 , 2 , 3 , 4 , 5 };
deque<int > q (a, a + 5 ) ;
vector<int > v = {1 , 2 , 3 , 4 , 5 };
deque<int > q (v.begin(), v.end()) ;
deque<int > q1 = {1 , 2 , 3 };
deque<int > q2 (dq1) ;
2. 双端队列的基本操作 操作 说明 q.push_back(x)在 q 的尾部入队 x q.push_front(x)在 q 的前端入队 x q.pop_back()弹出 q 队尾的元素 q.pop_front()弹出 q 队首的元素 q.front()返回 q 的队首元素 q.back()返回 q 的队尾元素 q.size()返回 q 中元素的数量 q.empty()返回 q 是否为空
3. 例题
题目描述 给定一个长度为 n(n<=10^6)的数组。有一个大小为 k 的滑动窗口从数组的最左端移动到最右端。你可以看到窗口中的 k 个数字。窗口每次向右滑动一个数字的距离。下面是一个例子:数组是 [1 3 -1 -3 5 3 6 7],k = 3。
窗口位置 最小值 最大值 [1 3 -1] -3 5 3 6 7 -1 3 1 [3 -1 -3] 5 3 6 7 -3 3 1 3 [-1 -3 5] 3 6 7 -3 5 1 3 -1 [-3 5 3] 6 7 -3 5 1 3 -1 -3 [5 3 6] 7 3 6 1 3 -1 -3 5 [3 6 7] 3 7
你的任务是得到滑动窗口在每个位置时的最大值和最小值。
输入格式 输入包括两行。
第一行包括 n 和 k,分别表示数组的长度和窗口的大小。
第二行包括 n 个数字。
输出格式 输出包括两行。
第一行包括窗口从左至右移动的每个位置的最小值。
第二行包括窗口从左至右移动的每个位置的最大值。
输入 #1
输出 #1 -1 -3 -3 -3 3 3 3 3 5 5 6 7
Code #include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
const int MAXN = 1e6 + 5 ;
int n, k, maxn[MAXN], minn[MAXN], a[MAXN];
deque<int > q1, q2;
int main () {
cin >> n >> k;
for (int i = 1 ; i <= n; i++) {
cin >> a[i];
}
for (int i = 1 ; i <= n; i++) {
if (q1. size () && q1.f ront() <= i - k) {
q1. pop_front ();
}
while (q1. size () && a[q1. back ()] > a[i]) {
q1. pop_back ();
}
q1. push_back (i);
if (i >= k - 1 ) {
minn[i - k + 1 ] = a[q1.f ront()];
}
}
for (int i = 1 ; i <= n; i++) {
if (q2. size () && q2.f ront() <= i - k) {
q2. pop_front ();
}
while (q2. size () && a[q2. back ()] < a[i]) {
q2. pop_back ();
}
q2. push_back (i);
if (i >= k - 1 ) {
maxn[i - k + 1 ] = a[q2.f ront()];
}
}
for (int i = 1 ; i <= n - k + 1 ; i++) {
cout << minn[i] << " " ;
}
cout << endl;
for (int i = 1 ; i <= n - k + 1 ; i++) {
cout << maxn[i] << " " ;
}
cout << endl;
return 0 ;
}
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
Gemini 图片去水印 基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online