文章目录
队列
队列概述
- 概述:队列是线性结构的一种,数组组成的队列是顺序队列,链表组成的队列是链队,还有一种特殊的队列概念上首位相邻叫做循环队列。
- 队列的特点
- 先进先出、后进后出
- 队列是在一端进行插入元素,在另一端进行删除元素,插入元素的一端叫做队尾,删除元素的一端叫做队头。尾进头出
- 队列的应用场景
- 网络数据传输:在网络传输中,队列可以用来存储待发送的数据包,保证数据的顺序和完整性。
- 任务调度:在操作系统中,队列可以用来存储待执行的任务,确保任务按照先后顺序执行。
- 消息队列:在分布式系统中,队列可以用来实现消息的异步传输和处理,提高系统的可伸缩性和可靠性。
- 事件处理:在事件驱动的系统中,队列可以用来存储待处理的事件,确保事件按照顺序被处理。
- 缓存管理:在计算机系统中,队列可以用来实现缓存管理,缓解系统压力,提高性能。
- 批处理:在数据处理系统中,队列可以用来存储待处理的数据,实现批量处理和优化资源利用。
- 系统监控:在监控系统中,队列可以用来存储待处理的监控数据,确保数据的实时性和完整性。
- 任务排队:在服务行业中,队列可以用来排队等待服务,确保服务的公平性和顺序性。
顺序队
- 概述:顺序队,是由数组实现的
结构体
c
typedef struct SqQueue{
int data[maxSize];
int front; // 队头指针
int rear; // 队尾指针
}SqQueue;
顺序队基本操作
初始化队列
c
void initQueue(SqQueue &qu){
qu.front = qu.rear = 0; // 队首和队尾指针重合
}
判断队空
c
int isEmpty(SqQueue qu){
if(qu.front == qu.rear){
return 1; // 头指针等于尾指针,队空
}else{
return 0; // 否则队不空
}
}
注意:队空返回 1 ,队列不空返回 0。
入队操作
c
int enQueue(SqQueue &qu , int x){
if(qu.rear == maxSize){ // 尾指针等于最大空间,队满
return 0;
}
// 队尾进元素
qu.rear += 1;
qu.data[qu.rear] = x; // 存入元素
return 1;
}
注意:入队需要判断队满,队满的条件是队尾指针等于数组最大空间数,但是这样,队头出元素会浪费很多时间
出队操作
c
int deQueue(SqQueue &qu , int &x){
if(qu.front == qu.rear){ // 队空,出队失败
return 0;
}
x = qu.data[qu.front]; // 元素赋值
qu.front -= 1; // 队头指针移动
return 1;
}
循环队列
-
概述:循环队列是由普通队列的演变而来,是因为普通队列在出队操作后,队列的头部空间就无法利用,导致空间的浪费,为了解决这一问题,循环队列产生了,循环队列就是将队列的头尾相连,形成一个环状结构,这样就可以循环利用头部空间避免浪费。
-
循环队列的特点
c// 队空 front == rear; // 头指针和尾指针 相等 // 队满 front = (rear + 1)%maxSize; rear = (front -1 + maxSize)%maxSize; // 入队、出队,均先移动指针 rear = (rear + 1)%maxSize; front = (front + 1)%maxSize;
结构体
c
typedef struct Queue{
int data[maxSize];
int front; // 队头指针
int rear; // 队尾指针
}Queue;
循环队列基本操作
初始化队列
c
void initQueue(SqQueue &qu){
qu.front = qu.rear = 0; // 队首和队尾指针重合
}
判断队空
c
int isEmpty(SqQueue qu){
if(qu.front == qu.rear){ // 队空
return 1;
}else{ // 队满
return 0;
}
}
入队操作
c
int enQueue(SqQueue &qu , int x){
if(qu.front == (qu.rear + 1)%maxSize){
return 0; // 队满,进队失败
}
/*队尾进元素,队头出元素*/
qu.rear = (qu.rear + 1)%maxSize; // 移动尾指针,指向空位置
qu.data[qu.rear] = x; // 存入元素
return 1;
}
出队操作
c
int deQueue(SqQueue &qu , int &x){
if(qu.front == qu.rear){
return 0; // 队空,出队失败
}
qu.front = (qu.front + 1)%maxSize; // 移动头指针,指向出队元素
x = qu.data[qu.front]; // 元素赋值
return 1;
}
链队
- 概述:链队,是由链表实现的
结构体
c
// 队列结点
typedef struct QNode{
int data;
struct QNode *next;
}QNode;
// 链队
typedef struct LiQueue{
QNode *front; // 队头指针
QNode *rear; // 队尾指针
}LiQueue;
链队的基本操作
初始化队列
c
void initQueue(LiQueue *&lqu){
lqu = (LiQueue *)malloc(sizeof(LiQueue));
lqu->front = lqu->rear = NULL;
}
判断队空
c
int isEmpty(LiQueue *lqu){
if(lqu->front == NULL || lqu->rear == NULL){
return 1;
}else{
return 0;
}
}
入队操作
c
void enQueue(LiQueue *lqu , int x){
QNode *p;
p = (QNode *)malloc(sizeof(QNode)); // 创建一个新结点
p->data = x;
p->next = NULL;
/*如果队列为空,则插入结点是首元结点,头、尾指针都指向*/
if(lqu->front == NULL || lqu->rear == NULL){
lqu->front = p;
lqu->rear = p;
}else{ // 新结点链接到队尾即可
lqu->rear->next = p;
lqu->rear = p;
}
}
注意:单链表入队,无需判断队满
出队操作
c
int deQueue(LiQueue *lqu , int &x){
QNode *p;
if(lqu->front == NULL || lqu->rear == NULL){
return 0; // 队空不能出队
}else{
p = lqu->front; // 减少冗余
if(lqu->front == lqu->rear){ // 只剩下首元结点
// p = lqu->front;
lqu->front = lqu->rear = NULL; // 指针指向,删除结点
}else{
// p = lqu->front;
lqu->front = p->next;
}
}
x = p->data;
free(p); // 释放结点
return 1;
}
注意:出队需要判断队空,链队入队和出队要注意 第一个元素的入队 和 最后一个元素的出队
队列的应用
循环队列双端都可插入删除
- 案例:如果允许在循环队列的两端都可以进行插入和删除操作,完成下述三个需求?
- 写出循环队列的类型定义
- 写出从队头删除和队尾插入的算法
- 写出从队尾删除和队头插入的算法
c
/* 循环队列的类型定义 */
typedef struct cyQueue{
int data[maxSize]; // maxSize 为已定义的常量
int front,rear;
}cyQueue;
/* 从队头删除和队尾插入的算法 */
/*队头删除元素*/
int deQueue(cyQueue &cqu , int &x){
if(cqu.front == cqu.rear){ // 队空
return 0;
}
cqu.front = (cqu.front + 1)%maxSize; // 指针移动,移到第一个删除元素
x = cqu.data[cqu.front];
return 1;
}
/*队尾进入元素*/
int enQueue(cyQueue &cqu , int x){
if(cqu.front == (cqu.rear + 1)%maxSize){ // 队满
return 0;
}
cqu.rear = (cqu.rear + 1)%maxSize; // 指针移动,移动到空位置插入元素
cqu.data[cqu.rear] = x;
return 1;
}
/* 从队尾删除和队头插入的算法 */
/*队尾删除元素*/
int deQueue(cyQueue &cqu , int &x){
if(cqu.front == cqu.rear){ // 队空
return 0;
}
x = cqu.data[cqu.rear]; // 先赋值
cqu.rear = (cqu.rear - 1 + maxSize)%maxSize; // 尾指针移动
return 1;
}
/*队头插入元素*/
int enQueue(cyQueue &cqu , int x){
if(cqu.rear == (cqu.front -1 + maxSize)%maxSize){
return 0; // 队满
}
cqu.data[cqu.front] = x;
cqu.front = (cqu.front - 1 + maxSize)%maxSize; // 头指针移动
return 1;
}
注意:头插尾山,是逆时针从大到小的顺序
循环链表表示队列,只设队尾指针
- 案例:假设以带头结点的循环链表表示队列,并且只设一个指针指向队尾结点,但不设头指针,请写出相应的入队列和出队列的算法
- 代码分析
- 循环链表有一个尾指针 rear
- 在循环链表尾部执行元素入队
- 在循环链表头部执行元素出队
c
/*入队列*/
void enQueue(LNode *&rear , int x){ // rear 指针发生改变
LNode *s = (LNode *)malloc(sizeof(LNode));
s->next = NULL;
s->data = x; // x 给结点赋值
/*头插法,将新结点插入循环链表尾部*/
s->next = rear->next;
rear->next = s;
rear = rear->next; // 指针后移
}
/*出队列:方式一*/
int deQueue(LNode *&rear , int &x){
LNode *p;
/*出队列判断队空*/
if(rear->next == rear){ // 只有头结点
return 0;
}else{
p = rear->next->next;
if(rear->next->next == rear){ // 只有首元结点,特殊处理
rear = rear->next;
}else{ // 不只有首元结点
rear->next->next = p->next;
}
x = p->data;
free(p);
return 1;
}
}
/*出队列:方式二*/
int deQueue(LNode *&rear , int &x){
LNode *p;
if(rear->next == rear){
return 0;
}else{
p = rear->next->next; // p 指向首元结点
rear->next->next = p->next; // 删除结点
x = p->data;
if(p == rear)
rear = rear->next;
free(p);
return 1;
}
}
二叉树层次遍历
- 代码分析
- 层次遍历要用到 队列,在这里我们使用循环队列,定义队列
- 根结点入队列,然后根节点出队列(并输出访问),然后检查当前结点的左右子树,左非空,左子树先入队列,右非空,右子树再入队列
- 然后当队列不空的时候一直循环,将队头元素弹出,重复 2)
c
void level(BTNode *bt){
/*定义队列,并初始化*/
BTNode *que[maxSize];
int front , rear;
front = rear = 0;
BTNode *p;
if(bt != NULL){
rear = (rear + 1)%maxSize;
que[rear] = bt;
while(front != rear){ // 队列不空
front = (front + 1)%maxSize;
p = que[front];
visit(p); // 访问 p
if(p->lChild != NULL){ // 左子书进入队列
que[(rear + 1)%maxSize] = p->lChild;
}
if(p->rChild != NULL){ // 右子树进入队列
que[(rear + 1)%maxSize] = p->rChild;
}
}
}
}