当前位置: 首页 > news >正文

数据结构:用数组实现队列(Implementing Queue Using Array)

目录

第1步:设计蓝图 (The Struct)

第2步:队列的诞生 (创建与初始化)

第3步:状态检查 (判满与判空)

第4步:核心操作 (入队与出队)

入队 (Enqueue)

出队 (Dequeue)

第5步:善后工作 (销毁队列)


现在,我们聚焦于如何用代码一步步地实现这个最终方案。我们从零开始,只写必要的代码,并在每一步都解释为什么这么写。


第1步:设计蓝图 (The Struct)

从第一性原理出发,要描述一个队列,我们需要知道哪些信息?

  1. 数据存放在哪里? -> 需要一个指针指向我们的数据区。 (int* data)

  2. 队列的总容量是多少? -> 需要一个变量记录容量,这样我们的取模运算才有依据。 (int capacity)

  3. 队头在哪里? -> 需要一个下标作为队头指针。 (int front)

  4. 队尾在哪里? -> 需要一个下标作为队尾指针。 (int rear)

把这些信息组合起来,就形成了我们的“蓝图”——结构体定义。

【代码实现 1:结构体定义】

#include <stdio.h>
#include <stdlib.h> // 我们需要用 malloc 和 free 来动态管理内存// 循环队列的蓝图
typedef struct {int* data;      // 指向一块用于存储数据的内存int capacity;   // 这块内存的总容量 (实际能存的元素数是 capacity-1)int front;      // 队头元素的下标int rear;       // 队尾元素的下一个位置的下标
} CircularQueue;

这就像建房子前画的设计图,它定义了我们的队列由哪几部分构成。


第2步:队列的诞生 (创建与初始化)

有了蓝图,我们就可以“施工”了,即创建一个具体的队列实例。创建一个容量为 k 的队列,需要做什么?

  1. CircularQueue 这个结构体本身分配一块内存。

  2. data 指针指向的数组分配一块内存。根据我们的最终方案,要存储 k 个元素,需要 k+1 的数组空间。

  3. 设置初始状态。一个空队列的初始状态是什么?根据我们的约定,是 frontrear 相等。最简单的就是都设为 0。

【代码实现 2:创建队列函数】

// 功能:创建一个能容纳 k 个元素的空队列
CircularQueue* createQueue(int k) {// 1. 为队列的整体结构分配内存CircularQueue* q = (CircularQueue*)malloc(sizeof(CircularQueue));// 防御性编程:检查内存是否分配成功if (!q) {perror("Failed to allocate memory for queue structure");return NULL;}// 2. 设置容量。注意,我们需要 k+1 个空间q->capacity = k + 1;// 3. 为实际存储数据的数组分配内存q->data = (int*)malloc(q->capacity * sizeof(int));// 再次进行防御性编程if (!q->data) {perror("Failed to allocate memory for queue data");free(q); // 如果数据区分配失败,必须释放之前为结构体分配的内存,防止内存泄漏return NULL;}// 4. 初始化头尾指针,表示队列为空q->front = 0;q->rear = 0;// 5. 返回创建好的队列实例return q;
}

第3步:状态检查 (判满与判空)

在进行核心操作(入队/出队)之前,必须先能准确判断队列的状态。这就像开车前要先看油表和仪表盘。

  • 判空 (isEmpty): 我们的约定是 front == rear

  • 判满 (isFull): 我们的约定是队尾的下一个位置是队头,即 (rear + 1) % capacity == front

这些是实现后续功能的基石。

【代码实现 3:状态判断函数】

// 功能:判断队列是否为空
int isEmpty(CircularQueue* q) {// 直接翻译我们的约定: front 等于 rearreturn q->front == q->rear;
}// 功能:判断队列是否为满
int isFull(CircularQueue* q) {// 直接翻译我们的约定: (rear + 1) 对 capacity 取模后等于 frontreturn (q->rear + 1) % q->capacity == q->front;
}

这两个函数非常简洁,但它们是整个循环队列逻辑的核心。


第4步:核心操作 (入队与出队)

现在,万事俱备,我们可以实现队列的两个核心灵魂操作了。

入队 (Enqueue)

要将一个值 value 加入队尾,需要三步:

  1. 检查:队列是不是已经满了?如果满了,就不能再入了。这是操作的“前置条件”。

  2. 放置:如果没满,就把 value 放到 rear 指向的位置。

  3. 更新rear 指针需要向前移动一位,为下一次入队做准备。别忘了,要用取模运算来实现循环。

【代码实现 4:入队函数】

// 功能:将一个元素 value 加入队尾
int enqueue(CircularQueue* q, int value) {// 1. 前置条件检查if (isFull(q)) {printf("入队失败:队列已满。\n");return 0; // 返回 0 表示失败}// 2. 放置数据q->data[q->rear] = value;// 3. 更新队尾指针,使用取模运算实现循环q->rear = (q->rear + 1) % q->capacity;return 1; // 返回 1 表示成功
}

出队 (Dequeue)

要从队头取出一个元素,逻辑类似:

  1. 检查:队列是不是空的?如果是空的,就无元素可取。这是操作的“前置条件”。

  2. 取出:如果没空,就从 front 指向的位置获取元素值。

  3. 更新front 指针需要向前移动一位,指向新的队头。同样,要用取模运算。

【代码实现 5:出队函数】

// 功能:从队头取出一个元素,并通过指针 pValue 返回该元素的值
int dequeue(CircularQueue* q, int* pValue) {// 1. 前置条件检查if (isEmpty(q)) {printf("出队失败:队列为空。\n");return 0; // 失败}// 2. 取出数据*pValue = q->data[q->front];// 3. 更新队头指针,使用取模运算实现循环q->front = (q->front + 1) % q->capacity;return 1; // 成功
}

第5步:善后工作 (销毁队列)

我们用 malloc 申请了内存,在程序结束前,作为一个负责任的程序员,必须将这些内存归还给操作系统,否则会造成内存泄漏。

销毁的顺序应该是:先释放内部的 data 数组,再释放队列结构体本身。

【代码实现 6:销毁函数】

// 功能:释放队列占用的所有内存
void destroyQueue(CircularQueue* q) {if (q != NULL) {// 1. 如果 data 指针有效,则先释放它指向的数组内存if (q->data != NULL) {free(q->data);}// 2. 再释放队列结构体本身的内存free(q);}
}

至此,我们已经从一张“蓝图”开始,一步步地构建了创建、检查、操作、销毁一个完整循环队列所需的所有代码。每一步的实现都紧密围绕着我们最初通过第一性原理推导出的核心约定。

http://www.xdnf.cn/news/1291825.html

相关文章:

  • Go 语言函数详解:从基础到高阶的行为逻辑构建
  • 洛谷 小 Y 拼木棒 贪心
  • 长篇音频制作(小说自动配音)完整教程
  • 15.卷积神经网络
  • 硬件工程师八月实战项目分享
  • 笔趣阁追书小说
  • Unity、C#常用的时间处理类
  • esp32s3 驱动pcm5102a 的 wav播放器,mqtt控制
  • Flutter网络请求实战:Retrofit+Dio完美解决方案
  • 微服务单元测试组件
  • 在CentOS 7上配置Android USB网络共享方式的方法
  • Linux的进程信号
  • ASP.NET 上传文件安全检测方案
  • 设计秒杀系统从哪些方面考虑
  • 微软正式将GPT-5接入Microsoft Copilot Studio(国际版)
  • 【物联网】基于树莓派的物联网开发【26】——树莓派开启串口并配置串口助手Minicom
  • jvm学习笔记之jvm的生命周期和发展历程
  • Ansible 实操笔记:Playbook 与变量管理
  • dubbo应用之门面设计模式
  • 《Python学习之基础语法2:掌握程序流程控制的艺术》
  • 101、【OS】【Nuttx】【周边】文档构建渲染:reStructuredText 格式
  • 【C语言强化训练16天】--从基础到进阶的蜕变之旅:Day3
  • C++多态:理解面向对象的“一个接口,多种实现”
  • 《AVL树的原理与C++实现:详解平衡二叉搜索树的高效构建与操作》
  • 旧版MinIO的安装(windows)、Spring Boot 后端集成 MinIO 实现文件存储(超详细,带图文)
  • 使用 6 种方法将文件从 Android 无缝传输到iPad
  • [Linux]学习笔记系列 -- [arm][process]
  • WPF 开发的瑞士军刀:Prism 框架从入门到精通指南
  • C++写文件,open函数的参数in、out、ate、app、trunc等标志分别是什么作用?
  • C++ 面向对象四大特性:面试深度解析