[入门必看]数据结构3.2:队列


第三章 栈、队列和数组

小题考频:23
大题考频:4


3.2 队列

难度:☆☆☆

知识总览

3.2.1_队列的基本概念

在这里插入图片描述

3.2.2_队列的顺序实现

在这里插入图片描述

3.2.3_队列的链式实现

在这里插入图片描述

3.2.4_双端队列

在这里插入图片描述
在这里插入图片描述


3.2.1_队列的基本概念

在这里插入图片描述

数据结构三要素——逻辑结构、数据的运算、存储结构(物理结构)

存储结构不同,运算的实现方式不同

在这里插入图片描述
队列(Queue)只允许在一端进行插入,在另一端删除线性表

逻辑结构:与普通线性表相同
数据的运算:插入、删除操作有区别

Queue排队:
在这里插入图片描述
从队尾插入,从队头删除。

  • 重要术语:队头、队尾、空队列
    在这里插入图片描述

队列的基本操作

InitQueue(&Q):初始化队列,构造一个空队列Q。
DestroyQueue(&Q):销毁队列。销毁并释放队列Q所占用的内存空间

创、销

EnQueue(&Q,x):进栈,若队列Q未满,将x加入,使之成为新的队尾
DeQueue(&Q,&x):出栈,若队列Q非空,删除队头元素,并用x返回。

增、删;
返回栈顶元素、删除栈顶元素

GetHead(Q,&x):读队头元素。若栈S非空,则用x返回栈顶元素

查:栈的使用场景中大多只访问栈顶元素;
返回栈顶元素、不删除栈顶元素

其他常用操作:
StackEmpty(S):判断一个栈S是否为空。若S为空,则返回true,否则返回false。


3.2.2_队列的顺序实现

在这里插入图片描述

初始化操作

  • 队列结构体:
#define MaxSize 10 //定义队列中元素的最大个数
typedef struct{
	ElemType data[Maxsize] //用静态数组存放队列元素
	int front,rear; //队头指针和队尾指针
} SqQueue; //Sq:sequence - 顺序

void testQueue(){
	SqQueue Q; //声明一个队列(顺序存储)
	//……
}

队头指针指向队头元素,队尾指针指向队尾元素的后一个位置
在这里插入图片描述

  • 初始化队列:
//初始化队列
void InitQueue(SqQueue &Q){
	//初始化时,队头、队尾指针指向0
	Q.rear = Q.front = 0; 
}

在这里插入图片描述

  • 判断队列为空:
bool QueueEmpty(SqQueue Q){
	if(Q.rear == Q.front) //队空
		return true;
	else //不空
		return false;
}

入队操作

——只能从队尾入队(插入)

  • 新元素入队:
//新元素入队
bool EnQueue(SqQueue &Q, ElemType x){
	if(队列已满) //队满,报错
		return false; 
	Q.data[Q.rear] = x; //将x插入队尾
	Q.rear = (Q.rear + 1)%MaxSize; //队尾指针加1取模
	return true; 
}

队列已满的条件:rear==MaxSize?????????
错!
当队头元素出队后,前面的位置空闲了,可以继续入队新元素。
在这里插入图片描述

  • Q.rear = (Q.rear + 1)%MaxSize;
    {0,1,2,…,MaxSize - 1}将存储空间在逻辑上变成了“环状”

取模运算,即取余运算。两个整数a,b,a%b == a除以b的余数
在《数论》中,通常表示为a MOD b

模运算将无限的整数域映射到有限的整数集合{0,1,2,…,b - 1}上;
模运算将存储空间在逻辑上变成了“环状”


循环队列 - 入队操作

在这里插入图片描述

队列已满的条件:队尾指针的再下一个位置是队头,即
(Q.rear+1)%MaxSize= = Q.front

如果再插入一个数据元素,rear和front指针指向同一个位置 - 是判断队空的条件,所以不可以再插入数据元素了。
代价:牺牲一个存储单元

代码实现:

//判断队列是否为空
bool QueueEmpty(SqQueue Q){
	if(Q.rear == Q.front) //队空条件
		return true;
	else
		return false;
}

//入队
bool EnQueue(SqQueue &Q,ElemType x){
	if((Q.rear + 1) % Maxsize == Q.front)
		return false; //队满则报错
	Q.data[Q.rear] = x; //新元素插入队尾
	Q.rear = (Q.rear + 1) % Maxsize; //队尾指针加1取模 - 用模运算将存储空间在逻辑上变成了“环状”
	return true;
}

循环队列 - 出队操作

在这里插入图片描述
删、查代码实现:

//出队(删除一个队头元素,并用x返回)
bool DeQueue(SqQueue &Q,ElemType &x){
	if(Q.rear == Q.front)
		return false; //队空则报错
	x = Q.data[Q.front];
	Q.front = (Q.front + 1) % MaxSize; //队头指针后移
	return true; 
}

//获得队头元素的值,用x返回
bool GetHead(SqQueue Q,ElemType &x){
	if(Q.rear == Q.front)
		return false;//队空则报错
	x=Q.data[Q.front];
	return true;

方案一:判断队列已满/已空

用队尾指针和队头指针的值计算出这个队列当中当前有多少个数据元素:

(rear + MaxSize - front) % MaxSize

在这里插入图片描述
初始化时,队尾指针和队头指针都指向同一结点

队空条件为:Q.rear == Q.front,即队尾指针和队头指针都指向同一结点。

队满条件为:(Q.rear+1)%MaxSize == Q.front,即队尾指针的再下一个位置是队头,此时就浪费了一个存储空间。

在这里插入图片描述

刁难!不允许浪费存储空间!
 
这种情况下,判断队空和队满的条件都是队尾指针和队头指针都指向同一结点,如何区分?


方案二:判断队列已满/已空

在队列结构中定义一个变量size,用来记录队列中存放了几个数据元素,开始时size的值设为0。

typedef struct{
	ElemType data[MaxSize];
	int front, rear;
	int size; //队列当前长度
} SqQueue;

队列元素的个数 = size

  • 初始化时rear = front = 0;
    size = 0;
  • 插入成功 size++;
    删除成功 size–;

在这里插入图片描述
虽然栈满和栈空时,队头指针和队尾指针都是指向同一个位置,但是由于定义了变量size,则可以用size来判断队满还是队空:

  • 队满时,size == MaxSize;
    队空时,size == 0;

方案三:判断队列已满/已空

还可以定义一个变量tag,当tag值为0时,表示最近执行过一次删除操作,当tag值为1时,表示最近执行过一次插入操作,开始时tag的值设为0。

typedef struct{
	ElemType data[MaxSize];
	int front, rear;
	int tag; //最近进行的是删除/插入
} SqQueue;
  • 初始化时rear = front = 0;
    tag = 0;
  • 每次删除操作成功时,都令tag = 0;
    每次插入操作成功时,都令tag = 1;

只有删除操作,才可能导致队空
只有插入操作,才可能导致队满
在这里插入图片描述

  • 如果是由于插入操作(tag = 1)导致队头队尾指针指向同一位置,此时队满:
    队满条件为:front == rear && tag == 1
  • 如果是由于删除操作(tag = 0)导致队头队尾指针指向同一位置,此时队空:
    队空条件为:front == rear && tag == 0

其他方法

——以上方法都是基于队尾指针指向队尾元素的下一个位置的前提条件
在这里插入图片描述

  • 实际情况中,也有可能遇到队尾指针是指向队尾元素的情况。
    在这里插入图片描述

这种情况下,入队操作时,先让队尾指针往后移一位,再将新的数据元素x插入队列。

所以初始化时,比较合理的方式是让front指针指向0这个位置;
让rear指针指向n-1位置:
在这里插入图片描述
此时插入第一个数据元素时,先让rear指针往后移一位,指向0,然后往这个位置插入新的数据元素x

  • 该设计方式判断队列为空的方法为:判断rear指针的下一个位置是不是front

那么该设计方式判断队列为满的方法不能使用:
(Q.rear+1)%MaxSize == Q.front 因为与队列为空相同。
在这里插入图片描述
合理的判满方法为:

  • 方案一:牺牲一个存储单元
    规定front指针前一个存储单元不可以存放数据元素,队满和队空时,两个指针的相对位置不同,可以区分。
  • 方案二:增加辅助变量
    size或tag,同上

3.2.3_队列的链式实现

在这里插入图片描述
——只能分别在队尾和队头进行增删操作的单链表(青春版),其也有带头结点的版本和不带头结点的版本。

代码定义队列:

typedef struct LinkNode{ //链式队列结点
	ElemType data;
	struct LinkNode *next;
}LinkNode;

typedef structi //链式队列
	LinkNode *front, *rear; //队列的队头和队尾指针
}LinkQueue;

入队时,用一个专门的尾指针指向最后一个结点,就不用从头往后寻找。
出队时,直接从头结点找到第一个数据结点,并删除即可。
在这里插入图片描述


初始化(带头结点)

typedef struct LinkNode{
	ElemType data;
	struct LinkNode *next;
}LinkNode;

typedef struct{
	LinkNode *front,*rear;
}LinkQueue;

//初始化队列(带头结点)
void InitQueue(LinkQueue &Q){
	//初始时front、rear都指向头结点
	Q.front = Q.rear = (LinkNode*)malloc(sizeof(LinkNode));
	Q.front->next = NULL;

//判断队列是否为空
bool IsEmpty(LinkQueue Q){
	if(Q.front == Q.rear)
		return true;
	else
		return false;
}

void testLinkQueue(){
	LinkQueue Q; //声明一个队列
	InitQueue(Q); //初始化队列
	//……
}

在这里插入图片描述

  • 判空:
    front指针和rear指针指向同一个结点,
    或者头结点的next指针是否指向NULL

初始化(不带头结点)

//初始化队列(不带头结点)
void InitQueue(LinkQueue &Q){
	//初始时front、rear都指向NULL
	Q.front=NULL;
	Q.rear=NULL;
}

//判断队列是否为空(不带头结点)
bool IsEmpty(LinkQueue Q){
	if(Q.front==NULL)
		return true;
	else
		return false;
}

在这里插入图片描述

  • 判空:
    front指针是否等于NULL,
    或rear指针是否等于NULL

入队(带头结点)

在这里插入图片描述

在这里插入图片描述


入队(不带头结点)

在这里插入图片描述
不带头节点,第一个元素入队的时候要特别处理,让front指针和rear指针都指向第一个结点。

接下来对rear指针指向的结点进行一个后插操作。
每次插入结点之后,都要让rear指针指向新的表尾结点。


出队(带头结点)

在这里插入图片描述
出队表尾结点需要特殊处理,需要修改表尾指针,指向头结点,让rear让front指向同一个位置,表示队列变成了空队列。


出队(不带头结点)

在这里插入图片描述
没有头结点,每次出队都要修改front指针指向。
最后一个结点出队之后,也要让front和rear都指向NULL,恢复成空队。


队列满的条件

在这里插入图片描述
顺序存储 - 静态数组空间有限;
连式存储 - 一般不会队满,除非内存不足


3.2.4_双端队列

演示

  • 栈的演示:
    请添加图片描述
  • 队列的演示:
    请添加图片描述
  • 双端队列的演示:
    请添加图片描述

对于双端队列,若只使用其中一端的插入、删除操作,则效果等同于栈。

  • 双端队列的两个变种:
    在这里插入图片描述

输入受限和输出受限

考查重点:判断输出序列合法性

Q:若数据元素输入序列为1,2,3,4,则哪些输出序列是合法的,哪些是非法的?

四个元素进行排列组合,总共可能会有24种输出顺序。

1,2,3,42,1,3,43,1,2,44,1,2,3
1,2,4,32,1,4,33,1,4,24,1,3,2
1,3,2,42,3,1,43,2,1,44,2,1,3
1,3,4,22,3,4,13,2,4,14,2,3,1
1,4,2,32,4,1,33,4,1,24,3,1,2
1,4,3,22,4,3,13,4,2,14,3,2,1
  1. 使用栈,有多少种合法序列:
    在这里插入图片描述
1,2,3,4 2,1,3,43,1,2,44,1,2,3
1,2,4,3 2,1,4,33,1,4,24,1,3,2
1,3,2,42,3,1,43,2,1,44,2,1,3
1,3,4,22,3,4,13,2,4,1 4,2,3,1
1,4,2,32,4,1,33,4,1,24,3,1,2
1,4,3,22,4,3,13,4,2,14,3,2,1

卡特兰数:
1 n + 1 C 2 n n = 1 4 + 1 C 8 4 = 14 \frac{1}{n+1}C_{2n}^{n}=\frac{1}{4+1}C_{8}^{4}=14 n+11C2nn=4+11C84=14

14种合法出栈序列
输出某个序号元素时,在其之前的所有元素都已输入队列。

  1. 使用输入受限的双端队列,有多少种合法序列:

栈中合法的序列,双端队列中一定也合法

在这里插入图片描述

1,2,3,4 2,1,3,43,1,2,44,1,2,3
1,2,4,3 2,1,4,33,1,4,24,1,3,2
1,3,2,42,3,1,43,2,1,44,2,1,3
1,3,4,22,3,4,13,2,4,1 4,2,3,1
1,4,2,32,4,1,33,4,1,24,3,1,2
1,4,3,22,4,3,13,4,2,14,3,2,1

输出某个序号元素时,在其之前的所有元素都已输入队列。

  1. 使用输出受限的双端队列,有多少种合法序列:

栈中合法的序列,双端队列中一定也合法

在这里插入图片描述

1,2,3,4 2,1,3,43,1,2,44,1,2,3
1,2,4,3 2,1,4,33,1,4,24,1,3,2
1,3,2,42,3,1,43,2,1,44,2,1,3
1,3,4,22,3,4,13,2,4,1 4,2,3,1
1,4,2,32,4,1,33,4,1,24,3,1,2
1,4,3,22,4,3,13,4,2,14,3,2,1

知识回顾与重要考点

3.2.1_队列的基本概念

在这里插入图片描述

  • 只能在队尾插入、在队头删除,先进先出(FIFO)

3.2.2_队列的顺序实现

在这里插入图片描述

  • 用静态数组存放队列
  • 静态数组容量有限,用模运算(取余)重复利用静态数组中各空闲的存储空间,将存储空间在逻辑上变为“环状”。

容易在选择题中考查:

  • 1、rear指针指向
    ①. 队尾元素后一个位置
    ②. 队尾元素

  • 2、所给条件应该如何判断队空和队满。
    a. 牺牲一个存储单元
    b. 增加size变量记录队列长度
    c. 增加tag=0/1用于标记

  • 思考:分别采用
    ①a、①b、①c
    ②a、②b、②c
    策略时,如何实现以下操作:
    1、初始化、入队、出队;
    2、判空、判满;
    3、计算队列长度


3.2.3_队列的链式实现

在这里插入图片描述

  • 入队只能在队尾;
    出队只能在队头
  • 首先关注是否带头结点
  • 第一个元素入队和最后一个元素出队可能有特殊处理
    特别注意要修改rear指针
  • 计算长度:从队头结点遍历,统计一共有多少结点
    时间复杂度为 O ( n ) O(n) O(n)
    如果频繁需要使用长度,加入一个int型变量length用来记录这个队列到底有多少个元素(合理的改造)

3.2.4_双端队列

——一种操作受限的线性表
在这里插入图片描述

  • 双端队列是队列的变种
  • 回忆:栈的变种——共享栈
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

H3T

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值