S10--循环队列
4.2循环队列
4.2.1循环队列的核心概念
首先通过表格对比循环队列与其他队列实现的差异:
| 特性对比 | 顺序队列 | 循环队列 | 链式队列 |
|---|---|---|---|
| 存储结构 | 数组(线性增长) | 数组(环形结构) | 链表(离散内存) |
| 空间利用率 | 较低(假溢出) | 较高(空间重用) | 按需分配 |
| 溢出条件 | rear达到数组末尾 | front和rear相邻 | 内存耗尽 |
| 实现复杂度 | 简单 | 中等 | 中等 |
| 性能特点 | 可能产生假溢出 | 高效空间利用 | 无溢出问题 |
4.2.2队列结构定义分析
#define MAXSIZE 10typedef struct stackcycle {int arr[MAXSIZE]; // 固定大小数组int front; // 队头指针int rear; // 队尾指针} stackcycle;
关键特性:
- 静态数组:使用固定大小的数组,不支持动态扩容
- 环形结构:通过取模运算实现指针循环移动
- 牺牲一个空间:用于区分队空和队满条件
函数详细讲解与问题分析
1. 初始化函数 ( InitQueue )
void InitQueue(stackcycle* q) {assert(q != NULL);q->front = 0;q->rear = 0;}
代码解读:
- 正确初始化front和rear为0
- 空队列条件: front == rear
- 注释掉的动态分配代码:说明你考虑了动态版本,但当前是静态实现
2. 队列长度计算 ( QueueLength )
int QueueLength(stackcycle* q) {assert(q != NULL);return (q->rear - q->front + MAXSIZE) % MAXSIZE;}
代码解读:
- 公式正确: (rear - front + MAXSIZE) % MAXSIZE 是循环队列的标准长度计算公式
- 处理循环:通过 % MAXSIZE 确保结果为正数
- 时间复杂度O(1):效率很高
数学原理:
- 当rear ≥ front时:长度 = rear - front
- 当rear < front时:长度 = rear - front + MAXSIZE
- 统一公式: (rear - front + MAXSIZE) % MAXSIZE
3. 判空与判满函数
判空函数 ( queueempty )
bool queueempty(stackcycle* q) {assert(q != NULL);if (q->front == q->rear) return true;return false;}
关键点:
- 判断逻辑正确: front == rear 表示队列为空
- 这是循环队列为空的标准判断条件
判满函数 ( queuefull )
bool queuefull(stackcycle* q) {assert(q != NULL);if ((q->rear + 1) % MAXSIZE == q->front) return true;return false;}
代码解读:
- 判断逻辑正确: (rear + 1) % MAXSIZE == front 是循环队列的判满条件
- 牺牲一个空间:队列最多容纳MAXSIZE-1个元素,用于区分空和满状态
循环队列的空满判断原理:
空队列: front == rear满队列: (rear + 1) % MAXSIZE == front (牺牲一个空间)
4. 入队操作 ( Enqueue )
bool Enqueue(stackcycle* q, int e) {assert(q != NULL);if (queuefull(q)) {printf("队列已满,无法插入元素%d\n", e);return false;}q->arr[q->rear] = e;q->rear = (q->rear + 1) % MAXSIZE; // 合并操作return true;}
图解入队过程:
入队前: front=0, rear=2插入元素e: arr[2] = e更新rear: rear = (2 + 1) % 10 = 3
5. 出队操作 ( Dequeue )
bool Dequeue(stackcycle* q, int* e) {assert(q != NULL);if (queueempty(q)) {printf("队列为空,无法出队\n");return false;}*e = q->arr[q->front];q->front = (q->front + 1) % MAXSIZE; // 合并操作return true;}
图解出队过程:
出队前: front=0, rear=3获取元素: *e = arr更新front: front = (0 + 1) % 10 = 1
6. 获取队头元素 ( Gethead )
bool Gethead(stackcycle* q, int* e) {assert(q != NULL);if (queueempty(q)) {return false;}*e = q->arr[q->front % MAXSIZE]; // 取模冗余return true;}
7. 打印函数 ( printf_t )
void printf_t(stackcycle* q) {assert(q != NULL);for (int i = q->front; i != q->rear; i = (i + 1) % MAXSIZE) {printf("%d ", q->arr[i]);}}
代码解读:
- 遍历逻辑正确:从front开始,到rear结束,使用取模实现循环遍历
- 终止条件: i != q->rear 确保遍历所有元素
- 时间复杂度O(n):需要访问每个元素
这是循环队列遍历的标准模式
4.2.3 循环队列的优势总结
核心优势
- 解决假溢出问题
- 通过循环使用数组空间,避免"假溢出"现象
- 空间利用率显著高于普通顺序队列
- 高效的空间重用
- 出队后的空间可以立即被新入队的元素使用
- 特别适合需要持续处理数据的场景
- 所有操作O(1)时间复杂度
- 入队、出队、获取队头等操作都是常数时间复杂度
- 性能稳定,适合高性能应用
- 实现相对简单
- 基于数组实现,内存局部性好
- 逻辑清晰,易于理解和调试
时间复杂度总结
| 操作 | 时间复杂度 | 关键要点 |
|---|---|---|
| 初始化 | O(1) | 设置指针初始位置 |
| 入队 | O(1) | 检查队满+赋值+移动指针 |
| 出队 | O(1) | 检查队空+取值+移动指针 |
| 判空/判满 | O(1) | 简单指针比较 |
| 获取队头 | O(1) | 直接访问数组元素 |
| 获取长度 | O(1) | 公式计算 |
| 遍历 | O(n) | 需要访问每个元素 |
适用场景推荐
- 固定大小的缓冲区:如串口通信缓冲区、网络数据包缓存
- 实时系统:需要确定性性能的应用
- 资源受限环境:内存有限但需要队列功能的场景
- 高性能计算:对操作时间复杂度有严格要求的应用
4.2.4循环队列的环形思维模式
掌握循环队列最重要的是理解环形思维模式:
- 物理线性,逻辑环形
- 内存是线性的,但通过指针的循环移动创造环形逻辑
- 取模运算是实现环形的关键
- 空满判断的艺术
- 牺牲一个空间来简化判断逻辑
- 这是空间换时间的典型例子
- 指针的循环移动
- 指针到达边界时自动回到起点
- 实现无限循环使用的效果
