深入理解队列:从概念到实现的完整指南

队列(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 个空位判满)无浪费(节点数量 = 元素数量)
实现复杂度中等(需处理循环逻辑和假溢出)简单(无需考虑容量和循环)
适用场景元素数量固定、需高效随机访问元素数量动态变化、无需预估容量

选择建议

  • 若队列元素数量可预估、追求极致效率,优先用顺序队列(数组缓存友好)。
  • 若队列元素数量不确定、需灵活扩容,优先用链式队列(空间利用率高,实现简单)。

四、队列的经典应用场景

队列的 “先进先出” 特性使其在需要 “顺序处理” 的场景中不可或缺:

  1. 任务调度队列
    操作系统的任务调度、打印机的打印队列、服务器的请求队列等,均按 “先到先服务” 原则处理,队列是核心数据结构。
  2. 广度优先搜索(BFS)
    图论中的 BFS 算法(如迷宫最短路径、二叉树层序遍历),必须用队列存储待访问的节点,保证按层级顺序遍历。
  3. 消息队列
    分布式系统中,消息队列(如 RabbitMQ、Kafka)本质是队列的延伸,用于解耦生产者和消费者,实现异步通信。
  4. 缓冲区设计
    如键盘输入缓冲区、网络数据缓冲区,数据按接收顺序存储,按顺序处理,避免数据混乱。

五、总结

队列是遵循 “先进先出” 规则的线性数据结构,核心操作包括入队、出队、取队头元素和判空。它的两种实现方式各有优劣,需根据实际场景选择。

理解队列的关键是抓住 “队头出、队尾入” 的操作限制,这使得它成为顺序处理、异步通信、广度优先搜索等场景的核心工具。掌握队列后,你可以尝试用它实现二叉树层序遍历、简单的任务调度器等,进一步加深对 “先进先出” 特性的理解~