3.2.1 队列的基本概念
回顾:数据结构三要素:逻辑结构、存储结构、数据运算
线性表:具有相同数据类型的 n 个数据元素的有限序列,其中 n 为表长
栈(Stack):只允许在一端进行插入或者删除操作的线性表
队列(Queue):只允许在一端进行插入,另一端删除的线性表
特点:先入先出(FIFO)
重要术语:队头、队尾、空队列
- 队头:允许删除的一端
- 队尾:允许插入的一端
(尾插头删)
3.2.2 队列的顺序实现
基本操作:创、增、删、查(队头元素)、判空判满(增删查前必要判断)
1、创
静态数组存放队列元素
队头、队尾两个指针
- 队头指针:指向队头元素
- 队尾指针:指向队尾元素的后一个位置(下一个应该插入的位置)
//队列的顺序实现
#define MaxSize 10
typedef struct{
ElemType data[MaxSize]; //静态数组存放队列元素
int front,rear; //队头和队尾指针
}SqQueue;
2、初始化
初始时队头、队尾指针指向0
//初始化队列
void InitQueue(SqQueue &Q){
//初始时队头、队尾指针指向0
Q.rear=Q.front=0;
}
3、判空判满
- 判空
bool QueueEmpty(SqQueue Q)
//判空操作
bool QueueEmpty(SqQueue Q) //判断不变化,则不需要传址
{
if(Q.rear==Q.front) //队空条件
return true;
else
return false;
}
- 判满
循环队列:用模运算将存储空间在逻辑上变成了环状
Q.data[Q.rear]=x; //新元素插入队尾
Q.rear=(Q.rear+1)%MaxSize; //队尾指针+1取模
队列已满的条件:队尾的指针的再下一个位置是队头,即
(Q.rear+1)%MaxSize==Q.front
代价:牺牲一个存储的单元
4、入队操作
只能从队尾入队
//入队
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;
}
5、出队操作与获取头元素
只能让队头元素出队
//出队(删除一个队头元素,并用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;
}
队列元素个数:
判空判满小结:
1、有一个存储单元被牺牲
- 队空条件:Q.rear==Q.front
- 队满条件:(Q.rear+1)%MaxSize==Q.front
- 队列元素个数:(Q.rear+MaxSize-Q.front)%MaxSize
2、不浪费存储空间
- 初始化时令size=0,记录现队列元素所用空间
- 队空条件:size==0
- 队满条件:size==MaxSize
- 队列元素个数=size
3.2.3 队列的链式实现
链式存储实现队列:
- 带头结点
- 不带头结点
基本操作:创、增、删、查(队头元素)、判空(增删查前必要判断)
不存在判满
基本操作:
1、创
//队列的链式实现
typedef struct LinkNode{ //链式队列结点
ElemType data;
struct LinkNode *next;
}LinkNode;
typedef struct{
LinkNode *front,*rear; //队列的队头和队尾指针
}LinkQueue;
- 带头结点
初始时,front、rear都指向头结点
//初始化队列(带头结点)
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;
}
- 不带头结点
初始时front、rear都指向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;
}
2、入队
void EnQueue(LinkQueue &Q,ElemType x)
- 带头结点
front不变
//新元素入队(带头结点)
void EnQueue(LinkQueue &Q,ElemType x){
LinkNode *s=(LinkNode *)malloc(sizeof(LinkNode)); //开辟空间创建新节点,存放数据
s->data=x;
s->next=NULL; //新结点next指向NIULL
Q.rear->next=s; //新结点插入rear之后
Q.rear=s; //修改尾指针
}
- 不带头结点
插入第一个元素时,修改队头队尾指针
非第一个元素,将结点插入rear之后,修改尾指针
//新元素入队(不带头结点)
void EnQueue(LinkQueue &Q,ElemType x){
LinkNode *s=(LinkNode *)malloc(sizeof(LinkNode));
s->data=x;
s->next=NULL;
if(Q.front==NULL) //插入第一个元素,修改队头队尾指针
{
Q.front=s;
Q.rear=s;
}
else{
Q.rear->next=s; //新结点插入rear之后
Q.rear=s; //修改尾指针
}
}
3、出队
(1)带头结点
结点p指向队头要删除的元素,记得释放结点p
若是最后一个结点出队,则需要修改rear指针
//出队(带头结点)
bool DeQueue(LinkQueue &Q,ElemType &x){
if(Q.front==Q.rear)
return false; //空队列
LinkNode *p=Q.front->next; //结点p指向队头要删除的元素
x=p->data;
Q.front->next=p->next; //头结点指向下一个
if(Q.rear==p) //如果是删除最后一个元素,需要更改尾指针
Q.rear=Q.front;
free(p); //释放p结点
return true;
}
(2)不带头结点
若是最后一个结点出队,front、rear指针都需要修改
//出队(不带头结点)
bool DeQueue(LinkQueue &Q,ElemType &x){
if(Q.front==NULL) //判空
return false;
LinkNode *p=Q.front; //结点p指向队头要删除的元素
x=p->data;
Q.front=p->next; //头结点移动到下一个位置
if(Q.rear==p) //如果是删除最后一个元素
{
Q.front=NULL;
Q.rear=NULL;
}
free(p);
return true;
}
队列满:
(1)顺序存储:预分配空间耗尽时队满;
(2)链式存储:一般不会队满,除非内存不足。
小结:
1、增:注意第一个元素入队
2、删:注意最后一个元素出队
3.2.4 双端队列
栈:只允许从一端插入或者删除的线性表
队列:只允许从一端插入,另一端删除的线性表
双端队列:只允许从两端插入、两端删除的线性表(若只使用一端插入删除,则效果等同于栈)
输入受限制的双端队列
输出受限制的双端队列
对输出序列合法性的判断:
1、栈:
卡特兰数
2、双端序列
栈中合法序列,双端序列中也一定合法