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

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

  • 第三章 栈、队列和数组
  • 3.2 队列
    • 知识总览
      • 3.2.1_队列的基本概念
      • 3.2.2_队列的顺序实现
      • 3.2.3_队列的链式实现
      • 3.2.4_双端队列
    • 3.2.1_队列的基本概念
      • 队列的基本操作
    • 3.2.2_队列的顺序实现
      • 初始化操作
      • 入队操作
      • 循环队列 - 入队操作
      • 循环队列 - 出队操作
      • 方案一:判断队列已满/已空
      • 方案二:判断队列已满/已空
      • 方案三:判断队列已满/已空
      • 其他方法
    • 3.2.3_队列的链式实现
      • 初始化(带头结点)
      • 初始化(不带头结点)
      • 入队(带头结点)
      • 入队(不带头结点)
      • 出队(带头结点)
      • 出队(不带头结点)
      • 队列满的条件
    • 3.2.4_双端队列
      • 演示
      • 考查重点:判断输出序列合法性
  • 知识回顾与重要考点
    • 3.2.1_队列的基本概念
    • 3.2.2_队列的顺序实现
    • 3.2.3_队列的链式实现
    • 3.2.4_双端队列


第三章 栈、队列和数组

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


3.2 队列

难度:☆☆☆

知识总览

3.2.1_队列的基本概念

[入门必看]数据结构3.2:队列_第1张图片

3.2.2_队列的顺序实现

[入门必看]数据结构3.2:队列_第2张图片

3.2.3_队列的链式实现

[入门必看]数据结构3.2:队列_第3张图片

3.2.4_双端队列

[入门必看]数据结构3.2:队列_第4张图片
[入门必看]数据结构3.2:队列_第5张图片


3.2.1_队列的基本概念

[入门必看]数据结构3.2:队列_第6张图片

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

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

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

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

Queue排队:
[入门必看]数据结构3.2:队列_第7张图片
从队尾插入,从队头删除。

  • 重要术语:队头、队尾、空队列
    [入门必看]数据结构3.2:队列_第8张图片

队列的基本操作

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_队列的顺序实现

[入门必看]数据结构3.2:队列_第9张图片

初始化操作

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

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

队头指针指向队头元素,队尾指针指向队尾元素的后一个位置
[入门必看]数据结构3.2:队列_第10张图片

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

[入门必看]数据结构3.2:队列_第11张图片

  • 判断队列为空:
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?????????
错!
当队头元素出队后,前面的位置空闲了,可以继续入队新元素。
[入门必看]数据结构3.2:队列_第12张图片

  • 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}上;
模运算将存储空间在逻辑上变成了“环状”


循环队列 - 入队操作

[入门必看]数据结构3.2:队列_第13张图片

队列已满的条件:队尾指针的再下一个位置是队头,即
(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;
}

循环队列 - 出队操作

[入门必看]数据结构3.2:队列_第14张图片
删、查代码实现:

//出队(删除一个队头元素,并用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

[入门必看]数据结构3.2:队列_第15张图片
初始化时,队尾指针和队头指针都指向同一结点

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

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

[入门必看]数据结构3.2:队列_第16张图片

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


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

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

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

队列元素的个数 = size

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

[入门必看]数据结构3.2:队列_第17张图片
虽然栈满和栈空时,队头指针和队尾指针都是指向同一个位置,但是由于定义了变量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;

只有删除操作,才可能导致队空
只有插入操作,才可能导致队满
[入门必看]数据结构3.2:队列_第18张图片

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

其他方法

——以上方法都是基于队尾指针指向队尾元素的下一个位置的前提条件
[入门必看]数据结构3.2:队列_第19张图片

  • 实际情况中,也有可能遇到队尾指针是指向队尾元素的情况。
    [入门必看]数据结构3.2:队列_第20张图片

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

所以初始化时,比较合理的方式是让front指针指向0这个位置;
让rear指针指向n-1位置:
[入门必看]数据结构3.2:队列_第21张图片
此时插入第一个数据元素时,先让rear指针往后移一位,指向0,然后往这个位置插入新的数据元素x

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

那么该设计方式判断队列为满的方法不能使用:
(Q.rear+1)%MaxSize == Q.front 因为与队列为空相同。
[入门必看]数据结构3.2:队列_第22张图片
合理的判满方法为:

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

3.2.3_队列的链式实现

[入门必看]数据结构3.2:队列_第23张图片
——只能分别在队尾和队头进行增删操作的单链表(青春版),其也有带头结点的版本和不带头结点的版本。

代码定义队列:

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

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

入队时,用一个专门的尾指针指向最后一个结点,就不用从头往后寻找。
出队时,直接从头结点找到第一个数据结点,并删除即可。
[入门必看]数据结构3.2:队列_第24张图片


初始化(带头结点)

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); //初始化队列
	//……
}

[入门必看]数据结构3.2:队列_第25张图片

  • 判空:
    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;
}

[入门必看]数据结构3.2:队列_第26张图片

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

入队(带头结点)

[入门必看]数据结构3.2:队列_第27张图片

[入门必看]数据结构3.2:队列_第28张图片


入队(不带头结点)

[入门必看]数据结构3.2:队列_第29张图片
不带头节点,第一个元素入队的时候要特别处理,让front指针和rear指针都指向第一个结点。

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


出队(带头结点)

[入门必看]数据结构3.2:队列_第30张图片
出队表尾结点需要特殊处理,需要修改表尾指针,指向头结点,让rear让front指向同一个位置,表示队列变成了空队列。


出队(不带头结点)

[入门必看]数据结构3.2:队列_第31张图片
没有头结点,每次出队都要修改front指针指向。
最后一个结点出队之后,也要让front和rear都指向NULL,恢复成空队。


队列满的条件

[入门必看]数据结构3.2:队列_第32张图片
顺序存储 - 静态数组空间有限;
连式存储 - 一般不会队满,除非内存不足


3.2.4_双端队列

演示

  • 栈的演示:
    [入门必看]数据结构3.2:队列_第33张图片
  • 队列的演示:
    [入门必看]数据结构3.2:队列_第34张图片
  • 双端队列的演示:
    [入门必看]数据结构3.2:队列_第35张图片

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

  • 双端队列的两个变种:
    [入门必看]数据结构3.2:队列_第36张图片

输入受限和输出受限

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

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

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

1,2,3,4 2,1,3,4 3,1,2,4 4,1,2,3
1,2,4,3 2,1,4,3 3,1,4,2 4,1,3,2
1,3,2,4 2,3,1,4 3,2,1,4 4,2,1,3
1,3,4,2 2,3,4,1 3,2,4,1 4,2,3,1
1,4,2,3 2,4,1,3 3,4,1,2 4,3,1,2
1,4,3,2 2,4,3,1 3,4,2,1 4,3,2,1
  1. 使用栈,有多少种合法序列:
    [入门必看]数据结构3.2:队列_第37张图片
1,2,3,4 2,1,3,4 3,1,2,4 4,1,2,3
1,2,4,3 2,1,4,3 3,1,4,2 4,1,3,2
1,3,2,4 2,3,1,4 3,2,1,4 4,2,1,3
1,3,4,2 2,3,4,1 3,2,4,1 4,2,3,1
1,4,2,3 2,4,1,3 3,4,1,2 4,3,1,2
1,4,3,2 2,4,3,1 3,4,2,1 4,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. 使用输入受限的双端队列,有多少种合法序列:

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

[入门必看]数据结构3.2:队列_第38张图片

1,2,3,4 2,1,3,4 3,1,2,4 4,1,2,3
1,2,4,3 2,1,4,3 3,1,4,2 4,1,3,2
1,3,2,4 2,3,1,4 3,2,1,4 4,2,1,3
1,3,4,2 2,3,4,1 3,2,4,1 4,2,3,1
1,4,2,3 2,4,1,3 3,4,1,2 4,3,1,2
1,4,3,2 2,4,3,1 3,4,2,1 4,3,2,1

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

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

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

[入门必看]数据结构3.2:队列_第39张图片

1,2,3,4 2,1,3,4 3,1,2,4 4,1,2,3
1,2,4,3 2,1,4,3 3,1,4,2 4,1,3,2
1,3,2,4 2,3,1,4 3,2,1,4 4,2,1,3
1,3,4,2 2,3,4,1 3,2,4,1 4,2,3,1
1,4,2,3 2,4,1,3 3,4,1,2 4,3,1,2
1,4,3,2 2,4,3,1 3,4,2,1 4,3,2,1

知识回顾与重要考点

3.2.1_队列的基本概念

[入门必看]数据结构3.2:队列_第40张图片

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

3.2.2_队列的顺序实现

[入门必看]数据结构3.2:队列_第41张图片

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

容易在选择题中考查:

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

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

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


3.2.3_队列的链式实现

[入门必看]数据结构3.2:队列_第42张图片

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

3.2.4_双端队列

——一种操作受限的线性表
[入门必看]数据结构3.2:队列_第43张图片

  • 双端队列是队列的变种
  • 回忆:栈的变种——共享栈

你可能感兴趣的:(#,第3章,栈,队列和数组,数据结构,java,算法)