小题考频:23
大题考频:4
难度:☆☆☆
数据结构三要素——逻辑结构、数据的运算、存储结构(物理结构)
存储结构不同,运算的实现方式不同
队列(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。
#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?????????
错!
当队头元素出队后,前面的位置空闲了,可以继续入队新元素。
取模运算,即取余运算。两个整数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
虽然栈满和栈空时,队头指针和队尾指针都是指向同一个位置,但是由于定义了变量size,则可以用size来判断队满还是队空:
还可以定义一个变量tag,当tag值为0时,表示最近执行过一次删除操作,当tag值为1时,表示最近执行过一次插入操作,开始时tag的值设为0。
typedef struct{
ElemType data[MaxSize];
int front, rear;
int tag; //最近进行的是删除/插入
} SqQueue;
插入操作(tag = 1)
导致队头队尾指针指向同一位置,此时队满:删除操作(tag = 0)
导致队头队尾指针指向同一位置,此时队空:——以上方法都是基于队尾指针指向队尾元素的下一个位置的前提条件
这种情况下,入队操作时,先让队尾指针往后移一位,再将新的数据元素x插入队列。
所以初始化时,比较合理的方式是让front指针指向0这个位置;
让rear指针指向n-1位置:
此时插入第一个数据元素时,先让rear指针往后移一位,指向0,然后往这个位置插入新的数据元素x
那么该设计方式判断队列为满的方法不能使用:
(Q.rear+1)%MaxSize == Q.front 因为与队列为空相同。
合理的判满方法为:
——只能分别在队尾和队头进行增删操作的单链表(青春版),其也有带头结点的版本和不带头结点的版本。
代码定义队列:
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); //初始化队列
//……
}
//初始化队列(不带头结点)
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指针和rear指针都指向第一个结点。
接下来对rear指针指向的结点进行一个后插操作。
每次插入结点之后,都要让rear指针指向新的表尾结点。
出队表尾结点需要特殊处理,需要修改表尾指针,指向头结点,让rear让front指向同一个位置,表示队列变成了空队列。
没有头结点,每次出队都要修改front指针指向。
最后一个结点出队之后,也要让front和rear都指向NULL,恢复成空队。
顺序存储 - 静态数组空间有限;
连式存储 - 一般不会队满,除非内存不足
对于双端队列,若只使用其中一端的插入、删除操作,则效果等同于栈。
输入受限和输出受限
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,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,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,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、rear指针指向
①. 队尾元素后一个位置
②. 队尾元素
2、所给条件应该如何判断队空和队满。
a. 牺牲一个存储单元
b. 增加size变量记录队列长度
c. 增加tag=0/1用于标记
思考:分别采用
①a、①b、①c
②a、②b、②c
策略时,如何实现以下操作:
1、初始化、入队、出队;
2、判空、判满;
3、计算队列长度