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