分享内容
- 队列的概念
- 队列的实现
- 循环队列的实现
计算机存储设备
计算机中包括三种类型的存储设备:硬盘(hard disk)、内存(random-access memory,
RAM)、缓存(cache memory)。硬盘保存长期存储的数据,包括操作系统、程序、文件等。内存临时存储当前运行的程序和正在处理的数据。缓存存储经常访问的数据和指令,减少CPU访问内存的次数。
生活中的排队现象
排队时,新来的人不断加入队列尾部,而位于队列头部的人买好奶茶,逐个离开。遵守排队规则!先到的人先买奶茶!
我们将排队的人替换为各种类型的元素(如整数、字符、结构体等),就得到了队列这种数据结构。
队列的概念
● 队列(queue)是一种遵循"先进先出"规则的线性数据结构。

● 允许删除的一端称为"队头"(队首),允许插入的一端称为"队尾"
● 没有元素的队列,称为空队列
●队列中元素的个数就是队列长度
队列的操作
● 入队操作push():插入一个元素,成为新的队尾
● 出队操作pop():删除队首元素
队列的操作
● 入队操作push():插入一个元素,成为新的队尾
● 出队操作pop():删除队首元素

队列的操作-练习
1)已知队列中依次有这些元素:13、2、11、34、41、77、5、7、18、26、15,其
中13是队首,那么第五个出队列的元素是?B
A. 5
B. 41
C. 77
D. 13

其他常用的操作:
● 查询容量:
· 返回队列中元素的数量size()
· 查询队列是否为空empty()
● 元素访问:
· 查看队首元素front()
· 查看队尾元素 back()
队列
1具有两个端口的容器,一个端口负责进,另一个端口负责出。
2数据存取特点:先进先出。

队列与栈
我们已经学习过两种线性的数据结构 -- 栈和队列
栈:先进后出,只允许在一端进行操作。
队列:先进先出,允许在两端进行操作。

在实际应用中,选择哪种数据结构取决于问题的具体需求。例如,
· 在深度优先搜索算法中使用栈存储待访问的节点。
· 在广度优先搜索算法中用队列来遍历树或图的节点。
数据结构
链表、树、图
后面都会学到
数据结构可分为"线性"和"非线性"两大类:
● 线性数据结构:数据按照一定顺序排列,元素之间是一对一的顺序关系;
● 非线性数据结构:元素之间是一对多,或者多对多的关系。

队列的突现
● 通常用一个数组模拟一个队列,用两个变量标记队列的首尾
例如,实现一个最多可以存放7个整型元素的队列
① 创建长度为7的数组 int q7 = {0};
② 创建一个变量表示队头位置 int head = 0;
③ 创建一个变量表示队尾位置 int tail = 0;

初始时,head == tail,队列为空。
● 在数组上实现队列的常用操作
①入队操作:将x入队,即一个人来排队,要站在队尾。void push(int x)
队列未满时,直接把x赋给队尾,再让队尾指针tail+1。

cpp
void push (int x) {
if(tail < 7)
q[tail] = x;
tail++;
}
}
x的数据类型要与队列元素类型一致
② 出队操作:删除队首。void pop()
队列不空时,队头指针head后移即可。

cpp
void pop () {
if(head != tail){
head++;
}
}
· 返回值类型void,只删除队头,不返回任何值
· 想获得队头可以用查询队头函数front()
③查询队头元素:int front()
队列不空时,返回队头元素,即qhead。

【注意】front()只查看不删除,指针不动。
cpp
int front(){
if(head != tail)
return q[head];
cout << "Queue is empty";
return -1;
}
· 返回值类型要与队列元素类型一致
好习惯:每次调用front()和pop()之前,都要用empty()检查一下,保证队列不为空。
④查询队尾元素:看一下谁排在最后一个。int back()
队列不空时,返回队尾元素,即qtail-1。

cpp
int back () {
if(head != tail)
return q[tail-1];
cout << "Queue is empty";
return -1;
}
· 返回值类型要与队列元素类型一致
⑤询问队列长度:int size()

cpp
队列元素个数=tail-head
int size() {
return tail-head;
}
⑥队列是否为空:bool empty()
cpp
队列空的条件是:tail == head
bool empty() {
return head == tail? true:false;
}
操作队列
输入7个整数,将这7个整数进行入队,接下来做三次出队操作,按照出队顺序输出出队元素,以上操作完成后输出此时的队首元素。
【输入格式】输入7个整数,用空格隔开。
【输出格式】输出2行,第1行输出出队元素,按照出队顺序输出,用空格
隔开。第2行输出完成出队操作后的队首元素。
【输入样例】4 3 9 5 12 6 7
【输出样例】4 3 9
5
1.完成队列的实现
- 队列是否为空empty()
- 入队函数 push()
- 出队函数 pop()
- 查询队首元素front()
2.完成主程序功能
- 输入7个整数,并依次入队
- 做3次出队操作,并输出
- 输出此时的队首元素
1.定义模拟队列的数组和变量(整数类型,数据长度是7)
cpp
#define MAXN 7
int q[MAXN] = {0};
int head = 0;// 队头位置
int tail = 0;// 队尾+1的位置

2.实现 empty 函数
cpp
bool empty () {
return head == tail? true:false;
}
3.push函数 (从队尾入队)
cpp
void push(int x) {
if( tail <MAXN){// 队列不满
q[tail] = x;
tail++;
}
4.pop函数 (从队头出队)
cpp
void pop () {
if(!empty()){// 队列不空
head++;
}
}

5.front函数(查询队头)
cpp
int front () {
if( !empty())
return q[head] ;
cout << "Queue is empty";
return -1;
}
好习惯:调用front()前,先查看队列是否为空,避免返回无效值。

cpp
#include<iostream>
using namespace std;
#define MAXN 7
int q[MAXN] = {0};
int head= 0;//指向队头
int tail =0;//指向队尾下一个
bool empty () {
return head == tail? true:false;
}
void push (int x) {
if(tail <MAXN){// 队列不满
q[tail] = x;
tail++;
}
}
void pop() {
if(!empty()){//队列不空
head++;
}
}
int front () {
if( !empty () )
return q[head] ;
cout << "Queue is empty";
return -1;
}
int main() {
int x;
for(int i = 1;i <= 7;i++) {
cin >> x;
push (x) ;
}
for(int i = 1;i <= 3;i++) {
cout << front() << '';
pop();
}
cout << endl;
cout << front () << '';
return 0;
}
队列的问题
问题:n个元素,反复出队、入队,长度为n的数组实现的队列会不够用。
因为head和tail会一直向右移动,当它们到达数组尾部时就无法继续移动了。

为了解决此问题,我们可以将数组想象成首尾相接的"环形数组"。

循环队列指针的变化

循环队列空满的判断


循环队列元素个数



循环队列总结
● 通过循环队列优化了空间利用率
● 假设循环队列的数组长度为MAXN,最多能存储(MAXN-1)个元素

循环队列一练习
1)设循环队列的数组长度为n,其头尾指针分别为f和r,则队列中元素个数为?D
A, r-f
B. r-f+1
C. (r -f) % n
D. (r+n -f) % n
围圈报数
有n个人依次围成一圈,从第1个人开始报数,数到第m个人出列,然后从下一个人重新报数1,数到第m个人又出列,.,如此反复到所有的人全部出列为止。设n个人的编号分别为1、2、 ... 、n,打印出列的顺序。
【输入描述】两个数n、m,中间空格隔开。1≤n,m≤100。
【输出描述】出列顺序
【输入样例】4 17
【输出样例】1 3 4 2
-
可以用队列模拟这个过程:最初有n个人在队列中,报数1~m-1的人从队首跑到
队尾(先出队再入队),报数m的人出队。

-
这个问题中每个人都有可能多次入队、出队,最好用循环队列,防止空间不够用。
实现循环队列
· 最多是n个人在队列中1≤n≤100
· 能放下n个人的循环队列,需要定义至少n+1长的数组






完整代码
cpp
#include<iostream>
using namespace std;
#define MAXN 101
int q[MAXN]={0};//循环队列,最多能存储(MAXN-1)个元素
int head=0;//指向队头
int tail=0;//指向队尾下一个
bool empty() {
return head == tail? true: false;
}
void push (int x) {
if((tail+1) % MAXN != head){// 队列不满
q[tail] = x;
tail = (tail+1) % MAXN;
}
}
void pop() {
if(!empty()){//队列不空
head = (head+1) % MAXN;
}
}
int front () {
if( !empty () )
return q[head] ;
cout << "Queue is empty";
return -1;
}
int main(){
int n,m;
cin >> n >> m;
for(int i=1;i <= n;i++){//n个编号依次入队
push (i) ;
}
// 开始报数,直到队列为空
int cnt=1;//报的数
while(!empty () ) {
if(cnt == m){//数到m,队首出队并输出
cout << front() << " ";
pop() ;
}
else[//其他数,队首出队,再入队(到队尾)
int x =front();// 队首
pop() ;
push (x) ;
}
cnt++;// 1~m循环报数
if (cnt == m+1)
cnt = 1;
}
return 0;
}

本次课程的知识点
-
队列的现实场景和概念
-
队列的操作
-
队列的实现
-
循环队列的实现
1、以下不是队列的基本操作的是(B)?
A、从队尾插入一个新元素
B、从队列中删除队尾元素
C、判断一个队列是否为空
D、读取队首元素的值
2、若用一个大小为6的数组来实现循环队列,当前tail和head的值分别为0和3。
当从队列中删除一个元素,再加入两个元素后,tail和head的值分别为:B
A、1和5
C、4和2
B、2和4
D、5和1
密码规则
密码由生日作为基数(8位数字),生成密码的规则如下:
- 将第1个数字删除,第2个数字放到数字序列末端,再将第3个数字放至
数字序列末端。 - 重复执行第一步,直到所有数字删除完毕。
- 删除的数字将会组成一串新的数字,这就是密码。
【输入格式】一行,一组八位数字。
【输出格式】一行,八位数字生成的密码。
【输入样例】18781205
【输出样例】18075281
cpp
#include<iostream>
using namespace std;
#define MAXN 9
int q[MAXN]={0};//循环队列,最多能存储(MAXN-1)个元素
int head= 0;// 指向队头
int tail= 0;//指向队尾下一个
bool empty() {
return head == tail? true: false;
}
void push (int x) {
if((tail+1) % MAXN != head ){//队列不满
q[tail] = x;
tail = (tail+1) % MAXN;
}
}
void pop () {
if(!empty()){//队列不空
head = (head+1) % MAXN;
}
}
int front () {
if( !empty () )
return q[head] ;
cout << "Queue is empty";
return -1;
}
int main() {
char ch[9];
cin >> ch;
for(int i=0;i <= 7;i++){//8个数字依次入队
push (ch[i]-'0');
}
//开始报数1~3,直到队列为空
int cnt= 1;//报的数
while(!empty () ) {
if(cnt == 1){//将第1个数字删除
cout << front();
pop() ;
}
else{ //第2、3个数字放到数字序列末端
int x = front () ; // MM
pop() ;
push (x) ;
}
cnt++;// 1~3循环报数
if (cnt == 4)
cnt = 1;
}
return 0;
}