戳一戳!和我一起走进信息学的世界
导读
信息学能够有助于孩子未来工作发展,提升孩子的综合能力。
前面课程中,我们深入讲解了栈结构,与栈对应的是队列结构,今天我们一起来探索队列的特点,队列的相关操作!
往期回顾
【NOIP竞赛/CSP认证】
▶ 赛前必看!信息学名师带你复习NOIP竞赛初赛及CSP认证初赛
【信息学精华帖】
▶ 收藏!交流会内容全公开,让你陪孩子更好地学习信息学
▶ 信息学的万般好处!附C++必备基础知识总结
▶ 信息学提高班知识体系详解与家长常见问题解答!让孩子赢在提高班学习的起跑线!
▶ 再回首,最全提高班知识总结,做更优秀的自己!
▶ 早知道!信息学集训班大揭秘!你想知道的的都在这!
【信息学集训】
▶ 01 温故知新,以更好状态学习数据结构和算法
▶ 02 信息学初赛必备计算机知识大串讲
▶ 03 位运算与进制初步
▶ 04 进制进阶与编码
▶ 05 字符串进阶操作
▶ 06 栈理论与实战
【数据结构前导课】
▶ 1 温故知新——一篇文章领略信息学C++知识结构
▶ 2 披荆斩棘——只学C++,可以做哪些竞赛题
▶ 3 运筹帷幄——一篇文章,让指针学起来也很简单!
▶ 4 初试锋芒——顺序表写起来也很简单
▶ 5 小试牛刀——STL库之vector数组
▶ 6 触类旁通——链表基本理论与信息学竞赛必考点
▶ 7 更进一步——STL库之List链表
▶ 8 知“人”善任——深入理解顺序表和链表的区别与应用
【C++提高班教程】
▶ C++强化 | 01 新学期再出发!温故知新!
▶ C++强化 | 02 继续前行,三大结构终极介绍
▶ C++强化 | 03 一维数组入门
▶ C++强化 | 04 数组越界
▶ C++强化 | 05 一维数组经典应用
▶ C++强化 | 06 一篇文章带你掌握字符数组
▶ C++强化 | 07 二维数组
▶ C++强化 | 08 二维数组经典案例
▶ C++强化 | 09 一篇文章带你探索函数的奥秘
▶ C++强化 | 10 函数进阶必备
▶ C++强化 | 11 这样学递归,才不会觉得难
▶ C++强化 | 12 格式化输入输出与文件操作
▶ C++强化 | 13 结构体入门
▶ C++强化 | 14 结构体进阶
【C++基础班教程】
▶ C++总结 | 01 程序的世界
▶ C++总结 | 02 输出、换行与注释
▶ C++总结 | 03 变量定义、赋值与运算
▶ C++总结 | 04 算术运算符与赋值运算符
▶ C++总结 | 05 cin语句
▶ C++总结 | 06 程序中的数据类型
▶ C++总结 | 07 数据类型补充
▶ C++总结 | 08 顺序结构
▶ C++总结 | 09 if 和 if-else
▶ C++总结 | 10 if嵌套与逻辑运算符
▶ C++总结 | 11 开关语句switch-case
▶ C++总结 | 12 for循环及其应用
▶ C++总结 | 13 数据范围与数据类型
▶ C++总结 | 14 break与continue
▶ C++总结 | 15 while与do-while
▶ C++总结 | 16 循环嵌套及其应用
1 队列-引入
上一节课我们讲了栈结构,栈结构是一个受限制的线性表。这节课我们来讲另一个受限制的线性表——队列。我们通过一个例子来理解。
我们现在有一个隧道,我们把砖块依次放进去,我们按照ABC的顺序依次放,就如下图所示:
我们发现,先放的砖块放在底下,后放的砖块放在上面。出来的时候,也是从下面依次出来,就像我们排队一样,先排队的先买,先离开。
2 队列
上面的例子,我们讲到的是队列结构,让我们接下来深入了解一下吧!
1 什么是队列
通过上面的示例,我们能够发现,这种结构只能从某一端插入数据,从另一端删除数据。
这种结构就是队列,队列也是一种操作受限制的线性表,普通的队列只能在某端插入数据,在另一端删除数据。不能在中间部分操作数据。队列结构示意图如下:
与队列有关的概念如下:
允许插入数据的一端,我们称之为队尾;允许删除数据的一端称之为队头。
插入数据的操作称之为入队;删除数据的操作称之为出队。
当队中没有数据的时候,我们称之为队空;当无法从队尾插入数据时,我们称之为队满。
2 队列结构特点
我们研究队列结构特点研究的是上面提到的普通队列。
由于只能从一端进入。从另一端出去,那先进队列的元素就会先从队列中出去,,所以队列结构的特点为“先进先出”。
3 循环队列
大家注意到,我们上面说队满的是无法从队尾插入数据而不是队列中数据占满了队列空间。这是因为,对于顺序结构的队列,不断入队和出队,就会造成下面这种情况:
即,前面的空间无法插入数据,导致空间浪费,为此,我们可以将队列的两端连起来,构成循环队列:
这样,即使队头元素出队,空出来的空间,也会成为新的队尾。
只不过,这种做法,在具体的实现中,要多考虑一些,我们在后面具体实现中再讲。
3 队列操作
讲完理论,我们一起来实现!
1 队列的定义与初始化
我们定义队列,需要有一个队头指针,指向队头元素所在位置,需要一个队尾指针,指向队尾元素所在位置后一个位置;
为什么要使用这种方式定义尾指针呢?
如果队尾指针指向队尾元素,我们让头指针和尾指针都指向同一个位置,就不太好描述怎么样队列为空,如果让尾指针指向队尾元素所在位置后一个位置,那么,头指针和尾指针重合,就说明队列中没有元素,如果尾指针是头指针的下一个位置,说明,头指针所指位置元素为队列中的唯一元素。
所以定义队列结构体如下:
struct Queue {
int data[10];
int front; //队头指针
int rear; //队尾指针
} Q;
定义了结构体之后,我们就可以实现初始化操作了,初始化操作,只需要将队头指针和队尾指针指向索引0即可:
int init(Queue &Q){
Q.front = Q.rear = 0;
return 0;
}
2 队列的置空与判空
如果队列为空,则,其队头指针和队尾指针指向同一个位置即可,所以置空和判空函数如下:
int clear(Queue &Q){
Q.rear = Q.front;
return 0;
}
bool empty(Queue Q){
return Q.front == Q.rear;
}
3 求队头元素
队头元素就是队头指针指向的数据:
int getHead(Queue Q){
return Q.data[Q.front];
}
4 判断队满
除了队空,还有队满的情况,对于普通的队列,当我们的尾指针指向表中的最后一个位置的后一个位置,就无法入队,这个时候,就是队满(10是队列容量)。
bool full(Queue Q){
return Q.rear == 10;
}
但其实,如果队头位置不在索引为0的地方,那队头位置之前的所有空位置就浪费了。为了解决这种问题,我们引入了循环队列,使用取余的方式,将普通的队列变为循环队列:
例如下图,虽然rear已经到了队尾,但是它指向下一个位置的时候,就可以从0开始:
就会变成:
我们可以一直填充数据到如下情况:
按理来说,上面的就是队满的情况。但是这样出问题了:队空也是队头指针和队尾指针指向同一个位置,队满也是如此。
为了解决上面这个问题,我们就要损失一个空间,也就是说,队满是如下的情况:
也就是说,当(rear+1)%10 == front的时候,就是队满。
bool full(Queue Q){
return (Q.rear+1)%10==Q.front;
}
5 入队
入队的时候,队尾指针要后移一位,可能会出现如下情况:
rear指向队尾了,也就是指向索引为5的位置,再插入,就应该指向索引为6的位置,但是没有6,我们按照循环队列,就需要指向索引为0的位置。所以我们要通过取余的方式。
当然我们也要考虑是否队满,如果队满,就无法入队。
int push(Queue &Q, int e){
if(full(Q)) return -1; //不能入队
Q.data[Q.rear] = e;
Q.rear = (Q.rear+1)%10;
return 0;
}
7 出队
入队要考虑队满,出队就要考虑队空,出队也是需要将队头指针指向其后面一个位置,但是如果后一个位置超出范围,要对容量取余。
int pop(Queue &Q){
if(empty(Q)) return -1; //不能出队
Q.front = (Q.front+1)%10;
return 0;
}
8 求队元素个数
考虑队元素个数,就要考虑两种情况,如果队头在队尾前面,用队尾减去队头就是队元素个数,如果队头在队尾后面,那就说明,队尾减去队头是一个负数,这个负数的绝对值,就是表中的空位置的个数,用总量减去空位置个数,就是队元素个数:
int len(Queue Q){
if(Q.rear>Q.front) return Q.rear - Q.front;
return Q.rear - Q.front + 10;
}
我们可以用取余运算简化:
int len(Queue Q){
return (Q.rear - Q.front + 10)%10;
}
4 练习
了解完了基础,我们就来做一些相关的练习吧!
1 趣味扑克牌
我们有一副扑克牌,将其中的1-9,每个数字取出一张,按照从小到大的顺序排序,小的放在上面,大的放在下面,做如下操作:
1、将最上面的牌放在最底下;
2、将最上面的牌扔掉;
一直重复上述两个操作,问最后一张扔掉的牌是什么?
思路
我们先可以自己分析这个流程的结果:
首先有123456789;
然后1放后面,2扔掉,3放后面,4扔掉,也就是奇数保留,偶数扔掉;
上述剩下13579;
然后9是放在后面的,所以1要扔掉,3留下,5扔掉,7留下,9扔掉;
上述剩下37,;
然后9是扔掉的,所以3要放在后面,7扔掉,最后只剩3。
然后3放在后面,然后3扔掉。
所以最后扔掉的是3
我们发现,这个过程就是不断入队和出队的过程:
首先,我们将1-9入队。
然后只要队不为空,就一直进行如下操作:
(1)获取头元素
(2)出队
(3)获取的元素入队
(4)出队
代码如下:
#include<iostream>
using namespace std;
struct Queue {
int data[10];
int front; //队头指针
int rear; //队尾指针
} Q;
int init(Queue &Q){
Q.front = Q.rear = 0;
return 0;
}
int clear(Queue &Q){
Q.front = Q.rear;
return 0;
}
bool empty(Queue Q){
return Q.front == Q.rear;
}
int getHead(Queue Q){
return Q.data[Q.front];
}
bool full(Queue Q){
return (Q.rear+1)%10==Q.front;
}
int push(Queue &Q, int e){
if(full(Q)) return -1; //不能入队
Q.data[Q.rear] = e;
Q.rear = (Q.rear+1)%10;
return 0;
}
int pop(Queue &Q){
if(empty(Q)) return -1; //不能出队
Q.front = (Q.front+1)%10;
return 0;
}
int len(Queue Q){
return (Q.rear - Q.front + 10)%10;
}
int main(){
init(Q);
for(int i=1;i<10;i++){
push(Q, i);
}
int e;
while(!empty(Q)){
e = getHead(Q);
pop(Q);
push(Q, e);
pop(Q);
}
cout<<e<<endl;
return 0;
}
执行结果如下:
5 作业
本节课的作业,就是复习上面的所有知识,并完成下面的题目!
1 括号匹配
设循环队列中数组的下标范围是1~n,其头尾指针分别为 f 和 r ,则其元素个数为()。
A:r-f
B:r-f+1
C:(r-f) % n + 1
D:(r-f+n) % n
AI与区块链技术
长按二维码关注