在数据结构中,队列(Queue)是一种非常基础且重要的线性数据结构,它遵循 “先进先出”(First-In-First-Out,FIFO)的原则,就像我们日常生活中排队买票一样,先到达的人先得到服务,后到达的人只能在队尾依次排队。本文将从队列的基本概念、核心操作、常见实现方式以及实际应用场景等方面,全面且深入地为大家讲解队列这种数据结构。
一. 概念和结构
1.1 概念
队列是一种特殊的线性表,它只允许在表的一端进行插入操作,在表的另一端进行删除操作。遵循“先进先出”(First-In-First-Out,FIFO)的原则
入队:进行插入操作的一端称为队尾
出队:进行删除操作的一端称为队头
底层结构选择
队列的底层用链表实现比较好,如果使用顺序表,出队列是的时间复杂度比较高,效率低
1.2 结构特性
- 先进先出(FIFO):这是队列最核心的特性,即最早进入队列的元素,会最早从队列中删除。
- 线性结构:队列的元素之间存在一对一的线性关系,除了队头和队尾元素外,每个元素都有唯一的前驱元素和后继元素。
- 操作受限:与普通线性表不同,队列的插入和删除操作被严格限制在队尾和队头,不允许在队列中间进行插入或删除操作。
二. 队列的实现
Queue.h(队列结构定义和函数定义)
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int QDataType;
//定义队列节点结构
typedef struct QueueNode {
QDataType data;
struct QueueNode* next;
}QueueNode;
//定义队列结构
typedef struct Queue {
QueueNode* phead;//对头
QueueNode* ptail;//队尾
//int size; //队列中有效数据个数
}Queue;
//初始化
void QInit(Queue* pq);
//销毁
void QueueDestory(Queue* pq);
//不存在头插尾插
//入队列——队尾入
void QueuePush(Queue* pq, QDataType x);
//出队列——队头出
void QueuePop(Queue* pq);
//判断队列是否为空
bool QueueEmpty(Queue* pq);
//取队头数据
QDataType QueueFront(Queue* pq);
//取队尾数据
QDataType QueueBack(Queue* pq);
//队列有效元素个数
int QueueSize(Queue* pq);
Queue.c(函数实现)
//初始化
void QInit(Queue* pq)
{
assert(pq);
pq->phead = NULL;
pq->ptail = NULL;
}
//入队——队尾入
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
//创建值为x的节点
QueueNode* newnode =(QueueNode*)malloc(sizeof(QueueNode));
if (newnode == NULL)
{
perror("malloc fail!");
exit(1);
}
newnode->data = x;
newnode->next = NULL;
//队列为空
if (pq->phead == NULL)
{
pq->phead = pq->ptail = newnode;
}
else {
pq->ptail->next = newnode;
pq->ptail = pq->ptail->next;
}
}
//判断队列是否为空
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->phead == NULL;
//相等返回true,不相等返回false
}
//出队——队头出
void QueuePop(Queue* pq)
{
//判断队列是否为空
assert(!QueueEmpty(pq));
//队列中只有一个节点
if (pq->phead == pq->ptail)
{
pq->phead = pq->ptail = NULL;
}
else {
QueueNode* next = pq->phead->next;
free(pq->phead);
pq->phead = next;
}
}
//取队头数据
QDataType QueueFront(Queue* pq)
{
assert(!QueueEmpty(pq));
return pq->phead->data;
}
//取队尾数据
QDataType QueueBack(Queue* pq)
{
assert(!QueueEmpty(pq));
return pq->ptail->data;
}
//队列有效元素个数
int QueueSize(Queue* pq)
{
assert(pq);
QueueNode* pcur = pq->phead;
int size = 0;
while (pcur)
{
size++;
pcur = pcur->next;
}
return size;
//该方法时间复杂度为O(N),如果数据过多,效率太低
//优化方法:在定义队列结构是,加入一个变量size,入队时,pq->size++,出队时,pq->size---.
//最后return pq->size即可
//return pq->size;
}
//销毁
void QueueDestory(Queue* pq)
{
assert(pq);
QueueNode* pcur = pq->phead;
while (pcur)
{
QueueNode* next = pcur->next;
free(pcur);
pcur = next;
}
pq->phead = pq->ptail = NULL;
}
(注:可自行创建测试文件去测试并实现一下以上代码,加深理解)
三. 队列的应用场景
3.1 任务调度
在操作系统中,队列常用于任务调度。例如,进程调度中的就绪队列,当多个进程都处于就绪状态等待 CPU 执行时,操作系统会按照一定的调度算法(如先来先服务调度算法),将这些进程依次放入就绪队列中,CPU 从队头取出进程进行执行,执行完成后再取下一个进程,这样就保证了进程按照 “先进先出” 的顺序得到执行(在先来先服务调度算法下)。
此外,在线程池、任务队列等场景中,也会使用队列来管理待执行的任务。当有新的任务产生时,将任务加入到任务队列的队尾;线程池中的线程从任务队列的队头取出任务进行执行,从而实现任务的有序调度和执行。
3.2 缓冲处理
在数据传输和处理过程中,当数据的产生速度和处理速度不匹配时,通常会使用队列作为缓冲。例如,在网络通信中,数据从发送方发送到接收方的过程中,由于网络带宽、传输延迟等因素的影响,数据的到达速度可能会不稳定。此时,接收方会设置一个接收缓冲区(队列),将接收到的数据先放入队列中,然后处理程序再从队列中按照 “先进先出” 的顺序取出数据进行处理。这样可以避免因数据到达速度过快而导致的数据丢失,同时也可以使处理程序能够稳定、高效地处理数据。
另外,在打印机打印文件的过程中,也会使用队列作为打印缓冲区。当多个用户同时向打印机发送打印请求时,这些打印请求会被依次放入打印队列中,打印机从队头取出打印请求进行打印,直到所有打印任务完成。
3.3 广度优先搜索(BFS)
在图和树的遍历算法中,广度优先搜索(Breadth-First Search,BFS)是一种非常重要的遍历方式,而队列是实现广度优先搜索的核心数据结构。
以树的层序遍历为例,层序遍历是指按照树的层次,从根节点开始,依次访问每一层的所有节点,同一层的节点按照从左到右的顺序访问。实现层序遍历的步骤如下:
- 将根节点入队。
- 当队列不为空时,取出队头节点,访问该节点。
- 将该节点的左子节点(如果存在)入队。
- 将该节点的右子节点(如果存在)入队。
- 重复步骤 2-4,直到队列为空,此时所有节点都已访问完毕。
在图的广度优先搜索中,原理与树的层序遍历类似,也是通过队列来记录待访问的节点,确保按照 “先访问距离起始节点近的节点,再访问距离远的节点” 的顺序进行遍历。
3.4 消息队列
在分布式系统和大型应用中,消息队列(Message Queue)是一种常用的组件,它基于队列的 “先进先出” 特性,实现了不同系统或模块之间的异步通信。
例如,在电商平台中,当用户下单成功后,需要进行一系列的后续操作,如库存扣减、订单状态更新、发送短信通知、生成物流单等。如果这些操作都在用户下单的主线程中同步执行,会导致用户等待时间过长,影响用户体验。此时,可以使用消息队列来实现异步处理:当用户下单成功后,主线程只需要将订单信息发送到消息队列中,然后立即返回给用户下单成功的结果;而其他相关的处理模块(如库存模块、通知模块、物流模块等)则监听消息队列,当接收到订单消息后,各自从消息队列中取出消息进行相应的处理。这样不仅提高了系统的响应速度,还降低了各个模块之间的耦合度,提高了系统的稳定性和可扩展性。
总结
队列在计算机科学领域有着广泛的应用,从操作系统的任务调度到分布式系统的消息队列,从数据缓冲处理到图和树的遍历算法,都离不开队列的支持。掌握队列的基本原理和实现方式,对于理解和设计高效的算法与系统具有重要的意义。
希望本文能够帮助大家更好地理解和掌握队列这种数据结构,如果在学习过程中遇到任何问题,欢迎在评论区留言讨论!