队列
深入理解队列:从概念到实现的完整指南
队列(Queue)是与栈并列的核心线性数据结构,它遵循 “先进先出” 的规则,在实际开发中应用广泛。本文将延续栈博客的风格,从核心概念、两种实现方式、特性对比到经典应用,带你全面掌握队列的原理与代码实现。
一、队列是什么?核心特性是什么?
生活中最典型的队列例子就是排队买票:
- 新来的人只能站在队伍末尾(“入队”)
- 只有队伍最前面的人才能买票离开(“出队”)
- 先排队的人先买票,后排队的人后买票
这种特性被称为 “先进先出”—— 最先进入队列的元素,最先被移出队列。
队列的关键术语:
- 队头(Front):队列的最前端(最先进入的元素)
- 队尾(Rear):队列的最后端(最后进入的元素)
- 入队(EnQueue):向队尾添加元素
- 出队(DeQueue):从队头移除元素
- 判空(IsEmpty):判断队列中是否有元素
- 取队头元素(GetFront):获取队头元素的值(不删除)
- 判满(IsFull):判断队列是否达到最大容量(仅顺序队列需要)
二、队列的两种实现方式
队列的实现同样有两种经典方式:顺序队列(用数组实现)和链式队列(用链表实现)。其中顺序队列需解决 “假溢出” 问题,链式队列则更灵活,我们分别详细实现。
方式 1:链式队列(链表实现)
链式队列用单链表存储元素,队头指向链表头节点(方便出队),队尾指向链表尾节点(方便入队),无需考虑溢出问题,空间利用率更高。
1. 结构定义
#pragma once
#include
#include
#include
#include
typedef int QDataType;
// 链式队列节点结构
typedef struct QueueNode
{
QDataType val;// 数据域
struct QueueNode* next;// 指针域(指向下一个节点)
}QNode;
//解决传二级指针的问题
// 链式队列管理结构(记录队头、队尾和长度,方便操作)
typedef struct Queue
{
QNode* phead;// 队头指针(指向头节点)
QNode* ptail;// 队尾指针(指向尾节点)
int size;// 队列中元素个数
}Queue;
2. 核心操作实现
(1)初始化队列
// 初始化链式队列
void QueueInit(Queue* pq);
// 初始化链式队列
void QueueInit(Queue* pq)
{
assert(pq);
//直接解引用就可改变头指针和尾指针
pq->phead = NULL;
pq->ptail = NULL;
//可看人数
pq->size = 0;
}
(2)创建新节点(辅助函数)
// 创建队列节点
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
ferror("malloc fail!");
return;
}
newnode->next = NULL;
newnode->val = x;
(3)入队(EnQueue)
// 入队:向队尾(链表尾部)添加节点
void QueuePush(Queue* pq, QDataType x);
// 入队:向队尾(链表尾部)添加节点
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
// 创建队列节点
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
ferror("malloc fail!");
return;
}
newnode->next = NULL;
newnode->val = x;
// 空队列:队头和队尾都指向新节点
if (pq->ptail == NULL)
{
pq->phead = pq->ptail = newnode;
}
else// 非空队列:尾节点next指向新节点,队尾更新
{
pq->ptail->next = newnode;
pq->ptail = newnode;
}
pq->size++;
}
(4)出队(DeQueue)
// 出队:删除队头(链表头部)节点
void QueuePop(Queue* pq);
// 出队:删除队头(链表头部)节点
void QueuePop(Queue* pq)
{
assert(pq);
assert(pq->size != 0);
// 一个节点
if (pq->phead->next == NULL)
{
free(pq->phead);
pq->phead = pq->ptail = NULL;
}
else//多个节点
{
QNode* next = pq->phead->next;
free(pq->phead);
pq->phead = next;
}
pq->size--;
}
(5)取队头元素
// 取队头元素
QDataType QueueFront(Queue* pq);
// 取队头元素
QDataType QueueFront(Queue* pq)
{
assert(pq);
assert(pq->phead);
return pq->phead->val;
}
(6) 取队尾元素
//找尾
QDataType QueueBack(Queue* pq);
//找尾
QDataType QueueBack(Queue* pq)
{
assert(pq);
assert(pq->ptail);
return pq->ptail->val;
}
(7) 找链式队列大小
//找大小
int QueueSize(Queue* pq);
//找大小
int QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
}
(8)判空
// 判断链式队列是否为空
bool QueueEmpty(Queue* pq);
// 判断链式队列是否为空
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->size == 0;
}
(9)销毁队列
// 销毁链式队列
void QueueDestroy(Queue* pq);
// 销毁链式队列
void QueueDestroy(Queue* pq)
{
assert(pq);
QNode* cur = pq->phead;
while (cur)
{
QNode* next = pq->phead->next;
free(pq->phead);
pq->phead = next;
cur = next;
}
pq->phead = pq->ptail = NULL;
pq->size = 0;
}
方式 2:顺序队列(数组实现)
顺序队列用数组存储元素,通过 “队头指针” 和 “队尾指针” 记录队列的边界。由于数组容量固定,直接使用会出现 “假溢出”(队尾已满但队头有空闲空间),因此实际中常用循环队列优化。
1. 结构定义
其他的思路就与顺序表类似,这里不过多阐述
#pragma once
#include
#include
#include
#include
typedef int QDataType;
// 链式队列节点结构
typedef struct QueueNode
{
QDataType* a;// 数组
int size;//有效数据个数
int capecity;//空间大小
}QNode;
循环队列设计思路:
- 队头指针front和队尾指针rear初始值均为 0
- 入队:rear = (rear + 1) % capacity(循环移动)
- 出队:front = (front + 1) % capacity(循环移动)
- 判空条件:front == rear
- 判满条件:(rear + 1) % capacity == front(预留一个空位区分空 / 满)
三、顺序队列 vs 链式队列:如何选择?
| 特性 | 顺序队列(循环队列) | 链式队列(链表实现) |
| 内存分配 | 连续内存,容量固定(可扩容) | 非连续内存,按需分配节点 |
| 操作效率 | 入队 / 出队效率高(O (1)) | 入队 / 出队效率高(O (1)) |
| 空间利用率 | 可能有浪费(预留 1 个空位判满) | 无浪费(节点数量 = 元素数量) |
| 实现复杂度 | 中等(需处理循环逻辑和假溢出) | 简单(无需考虑容量和循环) |
| 适用场景 | 元素数量固定、需高效随机访问 | 元素数量动态变化、无需预估容量 |
选择建议:
- 若队列元素数量可预估、追求极致效率,优先用顺序队列(数组缓存友好)。
- 若队列元素数量不确定、需灵活扩容,优先用链式队列(空间利用率高,实现简单)。
四、队列的经典应用场景
队列的 “先进先出” 特性使其在需要 “顺序处理” 的场景中不可或缺:
- 任务调度队列
操作系统的任务调度、打印机的打印队列、服务器的请求队列等,均按 “先到先服务” 原则处理,队列是核心数据结构。 - 广度优先搜索(BFS)
图论中的 BFS 算法(如迷宫最短路径、二叉树层序遍历),必须用队列存储待访问的节点,保证按层级顺序遍历。 - 消息队列
分布式系统中,消息队列(如 RabbitMQ、Kafka)本质是队列的延伸,用于解耦生产者和消费者,实现异步通信。 - 缓冲区设计
如键盘输入缓冲区、网络数据缓冲区,数据按接收顺序存储,按顺序处理,避免数据混乱。
五、总结
队列是遵循 “先进先出” 规则的线性数据结构,核心操作包括入队、出队、取队头元素和判空。它的两种实现方式各有优劣,需根据实际场景选择。
理解队列的关键是抓住 “队头出、队尾入” 的操作限制,这使得它成为顺序处理、异步通信、广度优先搜索等场景的核心工具。掌握队列后,你可以尝试用它实现二叉树层序遍历、简单的任务调度器等,进一步加深对 “先进先出” 特性的理解~
浙公网安备 33010602011771号