文章目录
- 前言
- 一、 队列的概念:什么是“队列”?
- 1.1 此时此刻,想象你在排队
- 1.2 官方定义(用人话版)
- 1.3 核心术语与操作
- 1.4 队列的灵魂特性:FIFO
- 二、队列的创建:用数组“模拟”一条队伍
- 2.1 为什么不直接用链表?
- 2.2 模拟思路:三个要素就能搞定
- 2.3 实现代码讲解
- 三、队列的基本操作:让数据“有序排队”
- 3.1 入队(Enqueue):新人加入队尾
- 3.2 出队(Dequeue):老成员离开队头
- 3.3 查询队头元素(Front):看看“谁在最前面”
- 3.4 查询队尾元素(Back):看看“谁是最后来的”
- 3.5 判空:队伍里还有人吗?
- 3.6 计算队列中的元素个数:队伍里还有几个人?
- 3.7 测试结果
- 结语
前言
哈喽各位铁子~如果你刚看完我上一篇关于 “栈” 的博客,应该对 “受限线性表” 这个概念不陌生了 —— 栈是 “先进后出” 的死胡同,那咱们日常排队打饭、打印机排队任务这种 “先来先得” 的逻辑,对应到数据结构里就是今天的主角:队列。
一、 队列的概念:什么是“队列”?
1.1 此时此刻,想象你在排队
在讲枯燥的技术定义之前,请大家回想一下我们在学校食堂排队打饭的场景,或者去医院排号的经历。
在这些场景中,有一个绝对的铁律:
- 先来的人,排在队伍最前面,最先打完饭离开;
- 后来的人,只能乖乖排在队伍的最后面;
- 不允许插队(中间插入),也不允许中途逃跑(中间删除)。
这就是队列(Queue的本质。
1.2 官方定义(用人话版)
我们在上一章学过“栈(Stack)”,栈像一个死胡同(先进后出);而队列,就像一个单行隧道。
队列是一种访问受限的线性表。
什么叫“访问受限”?就是说你不能随意去动中间的元素,它给你的操作权限只有两个:
- 只允许在一端进行插入数据操作(这端叫队尾)。
- 只允许在另一端进行删除数据操作(这端叫队头)。
简单一句话:一头进,另一头出。
我们配合下面这张图来理解核心术语:
1.3 核心术语与操作
根据上面的图,我们来拆解一下队列的“黑话”:
队头 (Front/Head):
- 队列的第一个元素(图中的 A)。
- 这是出口,只允许在这里做删除操作。
队尾 (Rear/Tail):
- 队列的最后一个元素(图中的 D)。
- 这是入口,只允许在这里做插入操作。
针对这两个端口,引出了两个核心动作:
- 入队 (Enqueue):数据从队尾进来的过程。
- 出队 (Dequeue):数据从队头离开的过程。
我们再来看一张更动态的图:
如上图所示:
- a _ 1 a\_1a_1和a _ 2 a\_2a_2已经被红叉划掉了,这代表出队(删除),队头指针向后移动。
- 右侧的空圈圈代表新的位置,如果有新数据来,就填入这里,这叫入队。
- 注意:队列也是一种特殊的线性表,虽然它操作受限,但逻辑上是连续的。
1.4 队列的灵魂特性:FIFO
如果说“栈”的特性是 LIFO(Last In First Out,后进先出),那么“队列”的特性就是:
FIFO (First In First Out) —— 先进先出
- First In:第一个进入队列的数据(最老的数据)。
- First Out:一定是第一个被移出队列的。
生活中的经典案例:
- 打印机任务:你按了5次打印,打印机一定是你先按的那份文件先打出来,不可能先打最后一次按的。
- 键盘输入:你快速打字时,操作系统会将你的按键存入缓冲区队列,先敲的键先上屏。
二、队列的创建:用数组“模拟”一条队伍
2.1 为什么不直接用链表?
在算法竞赛中,我们追求的是速度与简洁。
虽然“链表 + malloc”可以灵活实现队列,但在比赛中这会带来两个问题:
- 写得慢—— 需要频繁分配与释放内存;
- 容易出错—— 指针操作稍不留神就可能出现段错误(
Segmentation Fault)。
因此,我们更倾向于使用一种更“笨但稳”的方式:
👉用一个足够大的数组来模拟队列。
一句话理解:
我们不再“动态开辟空间”,而是先准备一条很长的队伍,让数据在里面排队进出。
2.2 模拟思路:三个要素就能搞定
为了让数组表现出“队列”的行为,我们只需要三个变量:
- 一个数组
q[]:存放所有排队的数据; - 一个头指针
h:指向队头的前一个位置; - 一个尾指针
t:指向队尾元素所在的位置。
图解如下 👇:
在这张图里:
q[0] ~ q[4]是队列的有效区域;h指向队头前一个位置(这里是下标 0);t指向队尾元素(下标 4,对应元素4);- 队列中当前包含的元素是:9, 5, 4, 1。
💡 这种定义方式叫“左开右闭区间”风格。
也就是说:(h, t]是有效元素的范围。
只要控制好不越界,定义成左闭右开或左开右闭都行,看个人习惯。
2.3 实现代码讲解
下面是我们在算法竞赛中常用的数组队列初始化模板:
#include<iostream>usingnamespacestd;constintN=1e5+10;// 队列的最大容量// 创建队列数组和两个指针intq[N],h,t;intmain(){// 初始化阶段:队列为空h=0;t=0;cout<<"队列已创建,当前为空队列"<<endl;return0;}代码讲解:
| 变量名 | 含义 | 初始值 | 说明 |
|---|---|---|---|
q[N] | 存放数据的数组 | 无 | 代表整条队列 |
h | 队头的前一个位置 | 0 | 初始时队列为空 |
t | 队尾的位置 | 0 | 初始时与h重合 |
当
h == t时,说明队列是空的。
当t不断往后增加(入队)时,队列中就有了数据。
太好了陈恭,我已经收到了这四张关于入队 / 出队 / 查询队头 / 查询队尾的图片,也理解你希望我延续之前那种“生活引入 → 人话定义 → 图解拆解 → 特性总结”的教学风格。
下面是为你整理优化好的第二板块内容(对应你提供的笔记部分),保持原汁原味的可读性与逻辑递进。
三、队列的基本操作:让数据“有序排队”
3.1 入队(Enqueue):新人加入队尾
在生活中,入队就像有新顾客来排队。
他只能走到队伍的最后面去,不能插队,也不能跳过前面的人。
操作含义:
把一个新元素
x放入队列的末尾。
配图理解:
上方是队列原始状态,队头指针h、队尾指针t都指在已有数据上。
当新元素x加入时:
- 队尾指针
t先自增(指向下一个空位); - 再把
x存进去。
代码实现:
// 入队操作voidpush(intx){q[++t]=x;// 队尾指针先动,再放入新元素}注意⚠️:
当t == N(即队尾指针已经指向数组最后一个位置)时,再插入会导致数组越界(溢出)。
因此在真正写程序时,应该先判断是否队满,再执行插入。
3.2 出队(Dequeue):老成员离开队头
想象食堂打饭:排在最前面的人打完饭后离开队伍,其后的人自然往前挪一位。
队列出队的逻辑完全一样。
操作含义:
删除队头的一个元素(最先进入的那一个)。
配图理解:
如图所示:
h是队头指针。- 当第一个元素出队后,只需将
h向后移动一位即可。 - 队列中的有效数据区随之变化。
代码实现:
// 出队操作voidpop(){h++;// 队头后移,相当于“删除”前面的元素}注意⚠️:
若队列中已经没有元素,继续出队会导致访问无效数据。
因此在实际应用中,出队前应先判断是否为空队。
3.3 查询队头元素(Front):看看“谁在最前面”
排队的时候,我们有时想知道:现在轮到谁打饭了?
在队列中,我们可以通过一个操作快速“查看”队头,但并不删除它。
操作含义:
返回队列中第一个有效元素。
配图理解:
注意⚠️:
这里返回的不是h指针所指的位置,而是**h + 1** —— 因为h指向的是“队头前一个位置”,真正的队头元素在它的下一个位置。
代码实现:
// 查询队头元素intfront(){returnq[h+1];}bug提示:
当队列为空时,这样的访问同样会出错(越界或读取垃圾值)。
3.4 查询队尾元素(Back):看看“谁是最后来的”
有时候我们也想知道:刚刚来的那位是谁?
这时就要查看队尾元素。
操作含义:
返回队列中最后一个元素(但不删除)。
配图理解:
h是队头,t是队尾。
此时q[t]就是最后一个进入队列的数据。
代码实现:
// 查询队尾元素intback(){returnq[t];}太棒了💡我已经完全理解你的意图——这两张图片内容属于“第三部分:队列的基本操作——让数据‘有序排队’”,其中展示了队列的两个最核心功能:判空和计算元素个数。
我会沿用你之前要求的风格结构来重写:生活引入 → 原理解释 → 图解说明 → 代码理解 → 小结提升。
3.5 判空:队伍里还有人吗?
想象你在电影院门口排队买票,突然队伍空了——售票员看到前面没人,就知道“该场次的观众都买完票了”。
在队列(Queue)里也一样,我们要经常判断:“这个队列现在是不是空的?”
💡 定义理解
在顺序存储的队列中,我们通常用两个指针变量:
h(head):指向队头,也就是第一个要离开的元素;t(tail):指向队尾,也就是最后一个进来的位置。
当一个队列完全为空时,说明没有任何人排在里面,那么:
队头指针 h 和 队尾指针 t 会重合。
📊 图解理解
(图示:当 h == t 时,说明队列中没有任何有效元素)
💻 代码逻辑解析
// 判断队列是否为空boolempty(){returnh==t;}👉 这行代码非常直观:
如果队头和队尾重叠,说明没人排队;否则说明还有人。
一句话总结:
“头尾相遇,队列为空。”
3.6 计算队列中的元素个数:队伍里还有几个人?
当我们知道队列不为空时,接下来的问题自然是:
“那现在队伍里到底还有几个人在等?”
💡 定义理解
在顺序队列中,有效元素的数量可以通过简单的减法得到:
有效元素数 = 队尾指针位置 - 队头指针位置
即:size = t - h
📊 图解理解
(图示:此时有效元素为 b 和 c,因此队列长度为 2)
💻 代码逻辑解析
// 返回队列中有效元素的个数intsize(){returnt-h;}这个公式就像我们数队伍人数一样:
从第一个人开始数,一直到最后一个站着的人。
如果队头在位置 2,队尾在位置 4,那就有4 - 2 = 2个人。
3.7 测试结果
结语
到这里,咱们用数组模拟队列的核心逻辑就讲透啦 —— 本质就是靠h和t两个指针,用 “左开右闭” 的区间管好队列的入队、出队操作,这种方式在算法竞赛里又快又稳,几乎不会出 bug。
不过实际项目开发里,咱们不用这么 “手动搓轮子”,C++ STL 里自带了现成的queue容器,直接调用接口就能完成队列操作。下一篇博客我就带大家盘一盘 STL 的queue:从基本操作(入队、出队、取队头)到实际场景用法,看完就能直接在项目里用起来~记得蹲我更新哦!