DA1-循环队列
代码实现
| C |
|---|
| #include <stdio.h>
#include <stdlib.h>
#define MAXSIZE 100 // 队列的最大容量
/**
* 循环队列的顺序存储结构定义
*/
typedef struct {
int data[MAXSIZE]; // 存储队列元素的数组
int front; // 队头指针(指向队头元素)
int rear; // 队尾指针(指向下一个入队位置)
} Queue;
/**
* 初始化队列
* @param q 指向队列的指针
*/
void init(Queue* q) {
q->front = 0; // 队头指针初始化为0
q->rear = 0; // 队尾指针初始化为0
}
/**
* 判断队列是否为空
* @param q 队列(传值调用)
* @return 空队列返回true,非空返回false
*/
bool IsEmpty(Queue q) {
return q.front == q.rear;
}
/**
* 判断队列是否已满
* @param q 队列(传值调用)
* @return 队满返回true,未满返回false
*/
bool IsFull(Queue q) {
return (q.rear + 1) % MAXSIZE == q.front;
}
/**
* 入队操作
* @param q 指向队列的指针
* @param x 要入队的元素
* @return 入队成功返回true,队满返回false
*/
bool enqueue(Queue* q, int x) {
// 检查队列是否已满
if ((q->rear + 1) % MAXSIZE == q->front) {
printf("队列已满,无法入队\n");
return false; // 队满,入队失败
}
// 在队尾插入元素
q->data[q->rear] = x;
// 队尾指针循环移动
q->rear = (q->rear + 1) % MAXSIZE;
return true; // 入队成功
}
/**
* 出队操作
* @param q 指向队列的指针
* @param x 指向存储出队元素的指针
* @return 出队成功返回true,空队列返回false
*/
bool dequeue(Queue* q, int* x) {
// 检查队列是否为空
if (q->front == q->rear) {
printf("队列为空,无法出队\n");
return false; // 空队列,出队失败
}
// 获取队头元素
*x = q->data[q->front];
// 队头指针循环移动
q->front = (q->front + 1) % MAXSIZE;
return true; // 出队成功
}
/**
* 获取队列中元素个数
* @param q 队列
* @return 队列中元素个数
*/
int getSize(Queue q) {
return (q.rear - q.front + MAXSIZE) % MAXSIZE;
}
/**
* 获取队头元素(不删除)
* @param q 队列
* @param x 指向存储队头元素的指针
* @return 获取成功返回true,空队列返回false
*/
bool getFront(Queue q, int* x) {
if (q.front == q.rear) {
return false; // 空队列
}
*x = q.data[q.front];
return true;
}
/**
* 清空队列
* @param q 指向队列的指针
*/
void clear(Queue* q) {
q->front = q->rear = 0;
}
|
循环队列结构图解
循环队列特点:
| Text Only |
|---|
| 普通队列:队满时无法继续插入
┌─────────────────────────┐
│ 1 │ 2 │ 3 │ │ │
├─────────────────────────┤
↑ ↑
front rear
循环队列:利用数组的循环特性
┌─────────────────────────┐
│ 3 │ │ │ 1 │ 2 │
├─────────────────────────┤
↑ ↑
rear front
|
队空和队满判断:
| Text Only |
|---|
| 队空条件:front == rear
队满条件:(rear + 1) % MAXSIZE == front
为什么牺牲一个存储单元?
- 区分队空和队满状态
- 队满时rear指向的位置不存储数据
|
队列操作流程图解
初始状态:
| Text Only |
|---|
| 队列:[ ][ ][ ][ ][ ][ ]
↑
front,rear=0
|
入队操作:
| Text Only |
|---|
| enqueue(1):
队列:[1][ ][ ][ ][ ][ ]
↑ ↑
front rear=1
enqueue(2):
队列:[1][2][ ][ ][ ][ ]
↑ ↑
front rear=2
enqueue(3):
队列:[1][2][3][ ][ ][ ]
↑ ↑
front rear=3
|
出队操作:
| Text Only |
|---|
| dequeue(&x):x=1
队列:[1][2][3][ ][ ][ ]
↑ ↑
front rear=3
dequeue(&x):x=2
队列:[ ][2][3][ ][ ][ ]
↑ ↑
front rear=3
|
循环特性:
| Text Only |
|---|
| 继续入队直到接近队满:
队列:[ ][ ][3][4][5][6]
↑ ↑
front rear=0
此时:(rear+1)%MAXSIZE = (0+1)%6 = 1 == front
队列已满,无法继续入队
|
复杂度分析
时间复杂度:O(1)
- 所有操作:初始化、判空、判满、入队、出队
- 原因:都是直接通过数组下标访问或指针移动
- 总体时间复杂度:O(1)
空间复杂度:O(n)
- 队列空间:数组
data[MAXSIZE]占用O(MAXSIZE)空间
- 辅助变量:front、rear等常数个变量
- 总体空间复杂度:O(n)
算法优化版本
| C |
|---|
| /**
* 优化版本:使用计数器避免牺牲存储单元
*/
typedef struct {
int data[MAXSIZE];
int front;
int rear;
int count; // 队列中元素个数
} OptimizedQueue;
void init_optimized(OptimizedQueue* q) {
q->front = 0;
q->rear = 0;
q->count = 0;
}
bool enqueue_optimized(OptimizedQueue* q, int x) {
if (q->count == MAXSIZE) {
return false; // 队满
}
q->data[q->rear] = x;
q->rear = (q->rear + 1) % MAXSIZE;
q->count++;
return true;
}
bool dequeue_optimized(OptimizedQueue* q, int* x) {
if (q->count == 0) {
return false; // 队空
}
*x = q->data[q->front];
q->front = (q->front + 1) % MAXSIZE;
q->count--;
return true;
}
bool IsEmpty_optimized(OptimizedQueue q) {
return q.count == 0;
}
bool IsFull_optimized(OptimizedQueue q) {
return q.count == MAXSIZE;
}
int getSize_optimized(OptimizedQueue q) {
return q.count;
}
|
安全版本
| C |
|---|
| /**
* 安全版本:添加完整的错误处理
*/
bool enqueue_safe(Queue* q, int x) {
// 参数检查
if (q == NULL) {
printf("队列指针为空\n");
return false;
}
// 队满检查
if ((q->rear + 1) % MAXSIZE == q->front) {
printf("队列已满\n");
return false;
}
// 入队操作
q->data[q->rear] = x;
q->rear = (q->rear + 1) % MAXSIZE;
return true;
}
bool dequeue_safe(Queue* q, int* x) {
// 参数检查
if (q == NULL || x == NULL) {
printf("参数错误\n");
return false;
}
// 队空检查
if (q->front == q->rear) {
printf("队列为空\n");
return false;
}
// 出队操作
*x = q->data[q->front];
q->front = (q->front + 1) % MAXSIZE;
return true;
}
|
循环队列与普通队列对比
| 特性 |
普通队列 |
循环队列 |
| 存储利用率 |
低(假溢出) |
高(充分利用) |
| 实现复杂度 |
简单 |
中等 |
| 空间浪费 |
可能很大 |
固定浪费1个单元 |
| 适用场景 |
简单应用 |
高效应用 |
测试用例
| C |
|---|
| /**
* 测试函数
*/
void test_queue_operations() {
Queue q;
init(&q);
printf("队列初始化完成\n");
printf("队列是否为空:%s\n", IsEmpty(q) ? "是" : "否");
// 测试入队
for (int i = 1; i <= 5; i++) {
if (enqueue(&q, i)) {
printf("入队:%d\n", i);
} else {
printf("入队失败:%d\n", i);
}
}
printf("队列大小:%d\n", getSize(q));
// 测试出队
int x;
while (dequeue(&q, &x)) {
printf("出队:%d\n", x);
}
printf("队列是否为空:%s\n", IsEmpty(q) ? "是" : "否");
}
|
算法特点
核心思想:
利用数组下标的循环特性,实现队列的循环存储,避免假溢出现象。
关键技术:
1. 循环下标:(index + 1) % MAXSIZE
2. 状态判断:通过front和rear的关系判断队空队满
3. 空间优化:充分利用数组存储空间
优势:
- 高效操作:所有基本操作都是O(1)时间复杂度
- 空间利用:避免假溢出,提高存储效率
- 实现简单:逻辑清晰,易于理解和维护
应用场景:
- 操作系统任务调度
- 网络数据包缓冲
- 广度优先搜索算法
- 生产者消费者问题
- 缓冲区管理
循环队列是队列数据结构的经典实现,在实际应用中具有很高的实用价值。