循环队列(顺序存储)完整解析与实现(数据结构专栏版)

循环队列(顺序存储)完整解析与实现(数据结构专栏版)

编写的这份代码完美实现了循环队列(环形队列)的核心功能,包括队列初始化、判空、入队、出队、打印等操作,采用顺序存储(数组)实现,符合数据结构中队列的 "先进先出(FIFO)" 特性,且通过取模运算解决了普通顺序队列的 "假溢出" 问题,是一份优秀的循环队列入门实现。下面结合数据结构专栏知识点,从核心结构、原理解析、功能拆解、运行结果、扩展知识点五个方面进行全面讲解。

一、循环队列核心结构解析(数据结构基础)

1. 宏定义与队列结构体定义

c

运行

复制代码
#define MAX 100  // 队列最大容量
typedef int DATATYPE;  // 队列数据类型别名,便于后续修改数据类型
typedef struct QUEUE
{
    DATATYPE elem[MAX];  // 顺序存储数组,存放队列元素
    int front;           // 队头指针:指向队列的第一个有效元素
    int rear;            // 队尾指针:指向队列的下一个空闲位置(尾后)
}QUEUE;
  • 数据结构知识点:循环队列采用顺序存储结构(静态数组),借助数组实现元素的连续存储,访问效率高(O (1));
  • 核心设计(循环队列标识):frontrear不再是单纯的递增指针,而是通过% MAX实现 "循环",解决普通顺序队列的 "假溢出"(数组前面有空闲空间,却因rear到达数组末尾无法入队);
  • 容量说明:MAX为数组长度,但循环队列的实际可用容量为MAX-1 ,这是因为采用(rear+1)%MAX == front作为判满条件,需要预留一个空闲位置区分 "队满" 和 "队空"(二者均满足front == rear,不预留会无法区分);
  • 可扩展性设计:使用typedef int DATATYPE定义数据类型别名,后续如需将队列存储字符、结构体等数据,仅需修改该宏定义,无需改动核心逻辑。

2. 队列初始化函数(InitQueue

c

运行

复制代码
QUEUE* InitQueue()
{
    // 1. 为队列结构体分配堆内存
    QUEUE *head=(QUEUE*)malloc(sizeof(QUEUE));
    // 注:可补充内存分配失败判断,提升健壮性
    // if(head == NULL) { printf("内存分配失败!\n"); exit(0); }
    
    // 2. 队头、队尾指针初始化为0,标识空队列
    head->front=head->rear=0;
    return head;
}
  • 核心功能:创建并初始化一个空的循环队列,返回队列结构体指针;
  • 初始化规则:空队列的frontrear指针相等,且均为 0,这是循环队列判空的核心依据;
  • 内存说明:队列结构体(包含指针front/rear和数组elem)分配在堆上,避免栈内存随函数调用结束释放,可在整个程序生命周期内使用。

二、循环队列核心原理(关键知识点)

1. 核心解决的问题:普通顺序队列的 "假溢出"

普通顺序队列中,frontrear仅单向递增,当rear到达数组末尾(rear == MAX-1)时,即使数组前面有空闲空间(因元素出队导致front后移),也无法继续入队,这就是 "假溢出"。

循环队列通过取模运算% MAX ,让frontrear指针到达数组末尾后,能够 "绕回" 数组起始位置,充分利用数组的空闲空间,解决假溢出问题。

2. 两个核心判断条件(循环队列的灵魂)

(1) 判空条件:front == rear

c

运行

复制代码
int IfEmpty(QUEUE *head)
{
    // 队头指针与队尾指针相等,标识队列为空
    if(head->front==head->rear)
    {
        return 1;  // 空队列返回1
    }
    return 0;  // 非空返回0
}
  • 原理:无论队列如何入队、出队,只要所有元素都被出队,front最终会与rear重合,标识队列无有效元素;
  • 注意点:该条件与普通顺序队列判空条件一致,但循环队列中frontrear可循环移动,含义更丰富。
(2) 判满条件:(rear + 1) % MAX == front

c

运行

复制代码
int InQueue(QUEUE *head,int num)
{
    // 循环判满:预留一个空闲位置,区分队满与队空
    if((head->rear+1)%MAX==head->front)
    {
        printf("Queue is full!\n");
        return 0;  // 入队失败返回0
    }
    // 后续入队逻辑...
    return 1;
}
  • 原理:通过预留一个空闲位置(rear指向的位置始终空闲),让队满时frontrear不重合,从而与队空条件(front == rear)区分开;
  • 为什么不直接用rear == front判满?若不预留空闲位置,队满和队空的条件均为front == rear,程序无法区分两种状态,会导致逻辑混乱;
  • 容量说明:数组长度为MAX,实际可存储的元素个数为MAX-1,这是循环队列顺序存储实现的一个小代价。

3. 循环队列的 "循环" 实现:取模运算

循环队列的入队、出队、打印操作中,均通过% MAX实现指针的循环移动,核心代码如下:

  1. 入队时rear指针更新:head->rear = (head->rear + 1) % MAX;
  2. 出队时front指针更新:head->front = (head->front + 1) % MAX;
  3. 打印时指针遍历:temp = (temp + 1) % MAX;
  • 示例:当rear = MAX-1(数组最后一个下标)时,(rear + 1) % MAX = 0rear绕回数组起始位置,实现 "循环"。

三、核心功能函数拆解(循环队列的操作实现)

1. 入队操作(InQueue):向队尾添加元素

c

运行

复制代码
int InQueue(QUEUE *head,int num)
{
    // 步骤1:判满,满队列无法入队
    if((head->rear+1)%MAX==head->front)
    {
        printf("Queue is full!\n");
        return 0;
    }
    // 步骤2:将元素存入rear指向的空闲位置
    head->elem[head->rear]=num;
    // 步骤3:更新rear指针,循环后移一位(指向新的空闲位置)
    head->rear=(head->rear+1)%MAX;
    // 步骤4:入队成功返回1
    return 1;
}
  • 操作规则:符合队列 "先进先出" 特性,仅允许在队尾添加元素;
  • 关键细节:先存元素,再更新rear指针,因为rear初始指向空闲位置,存入元素后才需要后移。

2. 出队操作(OUTQueue):从队头删除并返回元素

c

运行

复制代码
int OUTQueue(QUEUE *head)
{
    int iret=0;
    // 步骤1:判空,空队列无法出队
    if(head->rear==head->front)
    {
        printf("Queue is empty!\n");
        return 0;
    }
    // 步骤2:取出front指向的有效元素
    iret=head->elem[head->front];
    // 步骤3:更新front指针,循环后移一位(指向新的队头元素)
    head->front=(head->front+1)%MAX;
    // 步骤4:返回取出的元素
    return iret;
}
  • 操作规则:仅允许在队头删除元素,取出的是队列中最先入队的元素;
  • 关键细节:先取元素,再更新front指针,因为front初始指向第一个有效元素;
  • 注意点:该函数中,空队列返回0,但0也可能是合法的队列元素,可优化为通过返回值区分 "操作成功 / 失败",元素通过指针参数传出(更严谨)。

3. 打印队列(PrintQueue):遍历并输出所有有效元素

c

运行

复制代码
void PrintQueue(QUEUE *head)
{
    // 步骤1:定义临时指针,保存front初始位置(避免修改原front指针)
    int temp=head->front;
    // 步骤2:判空,空队列直接提示
    if(head->front==head->rear)
    {
        printf("Queue is empty!\n");
        return;
    }
    // 步骤3:循环遍历,从front到rear(不包含rear,因为rear指向空闲位置)
    while(temp!=head->rear)
    {
        printf("%d ",head->elem[temp]);
        // 步骤4:临时指针循环后移
        temp=(temp+1)%MAX;
    }
}
  • 核心技巧:使用临时指针temp遍历,避免修改队列的front指针(front是队头标识,不可随意改动);
  • 遍历边界:temp != head->rear,因为rear指向空闲位置,不是有效元素,遍历到rear前终止。

四、运行结果与解析

1. 预期运行结果(控制台输出)

plaintext

复制代码
Testing empty queue:
Queue is empty

Enqueuing elements: 10, 20, 30, 40, 50

Current queue: 10 20 30 40 50 

Dequeuing elements:
Dequeued: 10
Dequeued: 20

Queue after dequeue: 30 40 50 

Enqueuing more elements: 60, 70

Updated queue: 30 40 50 60 70 

Dequeuing all elements:
Dequeued: 30
Dequeued: 40
Dequeued: 50
Dequeued: 60
Dequeued: 70

Queue is now empty

2. 关键结果解析

  • 入队 5 个元素后,打印结果为10 20 30 40 50,符合入队顺序;
  • 出队 2 个元素后,打印结果为30 40 50,说明出队的是最先入队的1020,符合 "先进先出";
  • 后续入队6070后,打印结果为30 40 50 60 70,说明循环队列解决了 "假溢出",充分利用了数组空闲空间;
  • 清空队列后,再次判空返回1,说明队列操作逻辑闭环,无残留数据。

五、数据结构专栏扩展知识点(深化理解)

1. 循环队列的两种实现方式对比

实现方式 优点 缺点
顺序存储(数组,本文实现) 访问效率高(O (1)),实现简单 容量固定(静态数组),存在MAX-1的容量浪费
链式存储(链表) 容量动态扩展,无容量浪费 访问效率略低,需要额外存储指针域,实现稍复杂

2. 优化点:解决 "0 作为合法元素的歧义问题"

OUTQueue函数中,空队列返回0,但0也可能是合法元素,导致调用者无法区分 "队列空" 和 "取出的元素是 0"。优化方案如下:

c

运行

复制代码
// 优化后:通过返回值区分操作成功/失败,元素通过指针参数传出
int OUTQueue(QUEUE *head, DATATYPE *val)
{
    // 步骤1:判空
    if(head->front == head->rear)
    {
        printf("Queue is empty!\n");
        return 0;  // 0表示出队失败
    }
    // 步骤2:取出元素,通过指针传出
    *val = head->elem[head->front];
    // 步骤3:更新front指针
    head->front = (head->front + 1) % MAX;
    return 1;  // 1表示出队成功
}
  • 调用示例(修改main函数中的出队逻辑):

c

运行

复制代码
DATATYPE val;
if(OUTQueue(queue, &val))
{
    printf("Dequeued: %d\n", val);
}

3. 补充:队列的应用场景

循环队列在实际开发中应用广泛,典型场景包括:

  1. 任务调度(如操作系统的进程调度、打印机任务队列);
  2. 数据缓冲(如网络通信中的数据接收 / 发送缓冲、日志缓冲);
  3. 广度优先搜索(BFS)(如二叉树的层次遍历、图的最短路径搜索);
  4. 异步处理(如消息队列、事件驱动系统)。

六、总结

  1. 循环队列的核心价值是解决普通顺序队列的 "假溢出" 问题 ,通过% MAX取模运算实现指针循环移动,充分利用数组空间;
  2. 循环队列的关键是两个核心条件front == rear(判空)、(rear+1)%MAX == front(判满),且实际可用容量为MAX-1
  3. 队列的核心特性是先进先出(FIFO),所有操作均围绕 "队头出、队尾入" 展开;
  4. 这份代码是循环队列顺序存储的经典实现,吃透其原理后,可顺利过渡到链式队列、优先级队列等高级队列结构的学习。

这份代码完美契合数据结构专栏的核心知识点,是理解队列结构的绝佳案例,掌握循环队列的实现逻辑,能够为后续学习更复杂的数据结构和算法打下坚实基础

相关推荐
Yu_Lijing2 小时前
基于C++的《Head First设计模式》笔记——适配器模式
c++·笔记·设计模式
txinyu的博客2 小时前
C++ 单例模式
c++·单例模式
点云SLAM2 小时前
C++ 设计模式之工厂模式(Factory)和面试问题
开发语言·c++·设计模式·面试·c++11·工厂模式
亓才孓2 小时前
Java第三代时间API
java·开发语言
shangjian0072 小时前
AI大模型-机器学习-算法-线性回归-优化方法
人工智能·算法·机器学习
码农水水2 小时前
京东Java面试被问:Spring Boot嵌入式容器的启动和端口绑定原理
java·开发语言·人工智能·spring boot·面试·职场和发展·php
Yuer20252 小时前
状态不是变量:Rust 量化算子中的 State 工程语义
开发语言·后端·深度学习·机器学习·rust
玖釉-2 小时前
[Vulkan 学习之路] 05 - 缔结契约:创建逻辑设备 (Logical Device)
c++·windows·图形渲染
shangjian0072 小时前
AI大模型-机器学习-算法-逻辑回归
人工智能·算法·机器学习