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

c语言实现队列【由浅入深-数据结构】

文章目录

  • 前言
  • C语言实现队列的详细知识
    • 一、队列的基本概念
      • 队列的特性
    • 二、队列的实现方式
      • 1. 基于链表的实现(常用)
      • 2. 基于数组的实现
        • (1) 普通数组队列
        • (2) 循环队列(常基于数组实现,简单高效)
    • 三、队列的基本操作
      • 1. 初始化队列
      • 2. 销毁队列
      • 3. 入队操作
      • 4. 出队操作
      • 5. 获取队列元素数量
      • 6. 检查队列是否为空
      • 7. 获取队头元素
      • 完整代码实现
    • 四、数组队列与链式队列的比较
    • 五、实际应用
    • 六、注意事项
    • 七、总结


前言

本文介绍c语言实现队列的相关内容。

(【由浅入深】是一个系列文章,它记录了我个人作为一个小白,在学习c++技术开发方向计相关知识过程中的笔记,欢迎各位彭于晏刘亦菲从中指出我的错误并且与我共同学习进步,作为该系列的第一部曲-c语言,大部分知识会根据本人所学和我的助手——通义,DeepSeek等以及合并网络上所找到的相关资料进行核实誊抄,每一篇文章都可能会因为一些错误在后续时间增删改查,因为该系列按照我的网络课程学习笔记形式编写,我会使用绝大多数人使用的讲解顺序编写,所以基础框架和大部分内容案例会与他人一样,基础知识不会过于详细讲述)


C语言实现队列的详细知识

一、队列的基本概念

队列(Queue)是一种特殊的线性数据结构(特殊线性表),遵循先进先出(FIFO,First In First Out)的原则。这意味着最早被添加到队列中的元素将是最先被移除的元素。(可以类比景区排队等候时的队列)

队列的特性

  1. 队头(Front):进行删除操作的一端
  2. 队尾(Rear):进行插入操作的一端
  3. 基本操作
    • 入队(Enqueue):在队尾插入元素
    • 出队(Dequeue):从队头删除元素

队列在任务调度、消息队列、缓冲区处理等场景中广泛应用。

二、队列的实现方式

1. 基于链表的实现(常用)

链式队列使用链表数据结构来存储队列元素,避免了数组实现的容量限制和"假溢出"问题。

链式队列结构体

typedef int QDataType; // 队列存储数据类型typedef struct QueueNode {QDataType val;        // 存储的数据struct QueueNode *next; // 指向下一个节点的指针
} QueueNode;typedef struct Queue {QueueNode *head;      // 队头指针QueueNode *tail;      // 队尾指针
} Queue;

初始化

void QueueInit(Queue *pq) {pq->head = pq->tail = NULL;
}

入队操作

void QueuePush(Queue *pq, QDataType x) {QueueNode *newNode = (QueueNode *)malloc(sizeof(QueueNode));if (newNode == NULL) {perror("malloc failed");exit(1);}newNode->val = x;newNode->next = NULL;// 如果是空队列,更新head和tailif (pq->tail == NULL) {pq->head = pq->tail = newNode;} else {pq->tail->next = newNode;pq->tail = newNode;}
}

出队操作

void QueuePop(Queue *pq) {if (pq->head == NULL) {printf("Queue is empty!\n");return;}QueueNode *toDelete = pq->head;pq->head = pq->head->next;// 如果队列中只剩一个元素,更新tailif (pq->head == NULL) {pq->tail = NULL;}free(toDelete);
}

销毁队列

void QueueDestroy(Queue *pq) {QueueNode *cur = pq->head;while (cur) {QueueNode *next = cur->next;free(cur);cur = next;}pq->head = pq->tail = NULL;
}

2. 基于数组的实现

(1) 普通数组队列

普通数组队列在实现时,队头和队尾会不断后移,当队尾到达数组末尾时,无法继续在队尾插入元素,导致"假溢出"问题。

// 普通数组队列示例
#define MAXSIZE 20
typedef struct {int data[MAXSIZE];int front;  // 队头指针int rear;   // 队尾指针
} SqQueue;// 初始化
void InitQueue(SqQueue *Q) {Q->front = Q->rear = 0;
}
(2) 循环队列(常基于数组实现,简单高效)

循环队列(Circular Queue)是一种基于数组实现的队列数据结构,它通过将数组的首尾相连形成一个环形结构,使得队头和队尾指针在数组空间内循环移动。这种设计解决了普通队列的"假溢出"问题,实现了对存储空间的高效利用。

补充:普通队列的假溢出问题示例
假设有一个大小为5的数组队列,初始状态:

索引: 0  1  2  3  4
数据: -  -  -  -  -
front=0, rear=0

入队3个元素后:

索引: 0  1  2  3  4
数据: 1  2  3  -  -
front=0, rear=3

出队2个元素后:

索引: 0  1  2  3  4
数据: -  -  3  -  -
front=2, rear=3

此时队列还有2个空位(索引0和1),但队尾指针rear=3已接近数组末尾,无法再入队,这就是"假溢出"。


关键特点

  • 队空条件:front == rear
  • 队满条件:(rear + 1) % MAXSIZE == front
  • 为区分队空和队满,通常少用一个存储空间(实际能存储MAXSIZE-1个元素)

为什么需要多开一个空间?
这是循环队列的关键设计点。为了区分队空和队满,我们特意多开一个空间(即数组大小 = k + 1,其中k是队列最多能存储的元素数量)。如果没有多开一个空间,当队列满时,front == rear,与队空条件冲突,无法区分。

循环队列的核心操作

  1. 初始化
typedef struct {int *arr;       // 存储队列元素的数组int front;      // 队头指针int rear;       // 队尾指针int k;          // 队列容量(最多存储k个元素)
} MyCircularQueue;MyCircularQueue* myCircularQueueCreate(int k) {MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));obj->arr = (int*)malloc(sizeof(int) * (k + 1)); // 多开一个空间obj->front = 0;obj->rear = 0;obj->k = k;return obj;
}
  1. 判空
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {return obj->front == obj->rear;
}
  1. 判满
bool myCircularQueueIsFull(MyCircularQueue* obj) {return (obj->rear + 1) % (obj->k + 1) == obj->front;
}
  1. 入队操作
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {if (myCircularQueueIsFull(obj)) {return false; // 队列已满}obj->arr[obj->rear] = value; // 在rear位置插入元素obj->rear = (obj->rear + 1) % (obj->k + 1); // rear指针循环移动return true;
}
  1. 出队操作
bool myCircularQueueDeQueue(MyCircularQueue* obj) {if (myCircularQueueIsEmpty(obj)) {return false; // 队列为空}obj->front = (obj->front + 1) % (obj->k + 1); // front指针循环移动return true;
}
  1. 获取队头元素
int myCircularQueueFront(MyCircularQueue* obj) {if (myCircularQueueIsEmpty(obj)) {// 通常返回一个特殊值或抛出异常return -1;}return obj->arr[obj->front];
}
  1. 获取队尾元素
int myCircularQueueRear(MyCircularQueue* obj) {if (myCircularQueueIsEmpty(obj)) {return -1;}// rear指向的是最后一个元素的下一个位置,所以队尾元素在rear-1位置return obj->arr[(obj->rear - 1 + obj->k + 1) % (obj->k + 1)];
}
  1. 销毁队列
void myCircularQueueFree(MyCircularQueue* obj) {free(obj->arr);free(obj);
}
  • 循环队列的优缺点

    • 优点

      1. 空间利用率高:通过环形结构重复利用空位,避免"假溢出"。
      2. 操作效率高:入队和出队操作仅需指针移动,无元素搬移开销。
      3. 内存稳定:固定容量设计避免动态扩容带来的内存碎片。
      4. 实现简单:基于数组实现,指针操作清晰。
    • 缺点

      1. 容量固定:无法动态扩展,需要预先确定队列大小。
      2. 少量空间浪费:为区分队空队满,需要多开一个空间。
      3. 内存碎片:在极端情况下,可能会有少量内存浪费。

三、队列的基本操作

1. 初始化队列

  • 为队列分配内存空间
  • 设置队头和队尾指针
  • 对于循环队列,初始化front和rear为0

2. 销毁队列

  • 释放队列中所有节点的内存
  • 将队头和队尾指针置为NULL

3. 入队操作

  • 在队尾插入新元素
  • 对于循环队列,需要处理队尾指针的循环

4. 出队操作

  • 从队头删除元素
  • 对于循环队列,需要处理队头指针的循环

5. 获取队列元素数量

  • 对于数组队列:(rear - front + MAXSIZE) % MAXSIZE
  • 对于链式队列:需要额外维护一个计数器

6. 检查队列是否为空

  • 对于数组队列:front == rear
  • 对于链式队列:head == NULL

7. 获取队头元素

  • 返回队头元素的值,不修改队头指针
  • 需要先检查队列是否为空

完整代码实现

#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>// 定义队列中存储的数据类型为整数
typedef int QDataType;// 队列节点结构体定义
typedef struct QueueNode
{int val;            // 存储的元素值struct QueueNode* next; // 指向下一个节点的指针
}QNode;// 队列结构体定义(包含头指针、尾指针和元素数量)
typedef struct Queue
{QNode* phead;       // 队头指针(指向第一个元素)QNode* ptail;       // 队尾指针(指向最后一个元素)int size;           // 队列中元素的数量
}Queue;// 函数声明(队列操作接口)
void QueueInit(Queue* pq);                  // 初始化队列
void QueueDestroy(Queue* pq);               // 销毁队列(释放内存)
void QueuePush(Queue* pq, QDataType x);     // 入队操作
void QueuePop(Queue* pq);                   // 出队操作
QDataType QueueFront(Queue* pq);            // 获取队头元素
QDataType QueueBack(Queue* pq);             // 获取队尾元素
bool QueueEmpty(Queue* pq);                 // 检查队列是否为空
int QueueSize(Queue* pq);                   // 获取队列元素数量/* * 函数:QueueInit* 功能:初始化队列* 参数:pq - 指向队列结构体的指针* 返回值:无* 说明:将队头、队尾指针置为NULL,元素数量置为0*/
void QueueInit(Queue* pq)
{assert(pq);  // 确保指针不为空pq->phead = NULL;pq->ptail = NULL;pq->size = 0;
}/* * 函数:QueueDestroy* 功能:销毁队列(释放所有节点内存)* 参数:pq - 指向队列结构体的指针* 返回值:无* 说明:遍历链表释放所有节点内存,然后重置队列状态*/
void QueueDestroy(Queue* pq)
{assert(pq);  // 确保指针不为空QNode* cur = pq->phead;  // 从队头开始遍历while (cur){QNode* next = cur->next;  // 保存下一个节点指针free(cur);                // 释放当前节点cur = next;               // 移动到下一个节点}// 重置队列状态pq->phead = pq->ptail = NULL;pq->size = 0;
}/* * 函数:QueuePush* 功能:将元素插入队尾(入队操作)* 参数:pq - 指向队列结构体的指针*       x  - 要插入的元素值* 返回值:无* 说明:1. 分配新节点内存*       2. 处理空队列和非空队列两种情况*       3. 更新队尾指针和元素数量*/
void QueuePush(Queue* pq, QDataType x)
{assert(pq);  // 确保指针不为空// 分配新节点内存QNode* newnode = (QNode*)malloc(sizeof(QNode));if (newnode == NULL){perror("malloc fail");  // 打印错误信息return;}newnode->val = x;       // 设置节点值newnode->next = NULL;   // 新节点是队尾,next指向NULL// 情况1:队列为空(无元素)if (pq->ptail == NULL){pq->phead = pq->ptail = newnode;  // 队头和队尾都指向新节点}// 情况2:队列非空(已有元素)else{pq->ptail->next = newnode;        // 将当前队尾的next指向新节点pq->ptail = newnode;              // 更新队尾指针}pq->size++;  // 元素数量加1
}/* * 函数:QueuePop* 功能:移除队头元素(出队操作)* 参数:pq - 指向队列结构体的指针* 返回值:无* 说明:1. 检查队列是否为空(使用assert暴力检查)*       2. 处理单节点队列和多节点队列两种情况*       3. 更新队头指针和元素数量*/
void QueuePop(Queue* pq)
{assert(pq);  // 确保指针不为空assert(pq->phead != NULL);  // 确保队列非空(暴力检查)// 情况1:队列只有一个元素if (pq->phead->next == NULL){free(pq->phead);  // 释放队头节点pq->phead = pq->ptail = NULL;  // 重置队头和队尾}// 情况2:队列有多个元素else{QNode* next = pq->phead->next;  // 保存原队头的下一个节点free(pq->phead);                // 释放原队头节点pq->phead = next;               // 更新队头指针}pq->size--;  // 元素数量减1
}/* * 函数:QueueFront* 功能:获取队头元素的值(不移除元素)* 参数:pq - 指向队列结构体的指针* 返回值:队头元素的值* 说明:1. 检查队列是否为空*       2. 返回队头节点的值*/
QDataType QueueFront(Queue* pq)
{assert(pq);  // 确保指针不为空assert(pq->phead != NULL);  // 确保队列非空return pq->phead->val;  // 返回队头元素的值
}/* * 函数:QueueBack* 功能:获取队尾元素的值(不移除元素)* 参数:pq - 指向队列结构体的指针* 返回值:队尾元素的值* 说明:1. 检查队列是否为空*       2. 返回队尾节点的值*/
QDataType QueueBack(Queue* pq)
{assert(pq);  // 确保指针不为空assert(pq->ptail != NULL);  // 确保队列非空return pq->ptail->val;  // 返回队尾元素的值
}/* * 函数:QueueEmpty* 功能:检查队列是否为空* 参数:pq - 指向队列结构体的指针* 返回值:true(队列为空)或false(队列非空)* 说明:通过比较元素数量判断队列是否为空*/
bool QueueEmpty(Queue* pq)
{assert(pq);  // 确保指针不为空return pq->size == 0;  // 如果size为0则队列为空
}/* * 函数:QueueSize* 功能:获取队列中元素的数量* 参数:pq - 指向队列结构体的指针* 返回值:队列中元素的数量* 说明:直接返回队列结构体中的size字段*/
int QueueSize(Queue* pq)
{assert(pq);  // 确保指针不为空return pq->size;  // 返回元素数量
}/* * 主函数:测试队列实现* 功能:演示队列的基本操作* 说明:1. 初始化队列*       2. 入队1、2*       3. 打印队头(1)*       4. 出队(移除1)*       5. 入队3、4*       6. 依次出队并打印(2,3,4)*       7. 销毁队列*/
int main()
{Queue q;QueueInit(&q);QueuePush(&q, 1);QueuePush(&q, 2);printf("%d ", QueueFront(&q));  // 输出:1QueuePop(&q);  // 移除队头元素1QueuePush(&q, 3);QueuePush(&q, 4);// 依次出队并打印所有元素while (!QueueEmpty(&q)){printf("%d ", QueueFront(&q));  // 打印队头元素QueuePop(&q);                   // 移除队头元素}// 输出:2 3 4QueueDestroy(&q);return 0;
}

四、数组队列与链式队列的比较

特性数组队列(循环队列)链式队列
存储空间预先分配固定大小动态分配,无需预先指定容量
空间利用率可能有空间浪费(少用一个位置区分队空队满)高效利用空间
实现复杂度简单,但需处理循环逻辑相对简单
操作时间复杂度O(1)O(1)
队列大小限制有最大容量仅受内存限制
内存效率无额外指针开销每个节点有指针开销
适用场景预知队列大小,需要高效访问队列大小不确定,需要动态扩展

五、实际应用

队列在计算机科学中有广泛的应用:

  1. 任务调度:操作系统中的进程调度
  2. 消息队列:用于解耦系统组件,如RabbitMQ、Kafka
  3. 缓冲区:如打印机队列、网络数据包处理
  4. 广度优先搜索:图的遍历算法
  5. 打印任务管理:多个用户提交的打印任务排队处理

六、注意事项

  1. 循环队列的队空与队满区分

    • 通常采用"少用一个存储空间"的方法
    • 也可以使用额外的计数器来记录队列元素数量
  2. 链式队列的内存管理

    • 必须在队列销毁时释放所有节点内存
    • 防止内存泄漏
  3. 线程安全

    • 在多线程环境下,需要考虑队列操作的同步问题
    • 可以使用互斥锁等机制保证线程安全

七、总结

C语言实现队列主要有两种方式:基于数组的循环队列和基于链表的链式队列。循环队列适合队列大小已知的场景,而链式队列则更适合队列大小不确定的场景。

无论哪种实现方式,队列都保持了其先进先出的特性,使得它在处理需要按顺序处理的元素序列时非常有用。在实际应用中,需要根据具体需求选择合适的实现方式,并注意处理队空、队满等边界条件。

http://www.dtcms.com/a/537073.html

相关文章:

  • Flink ExecutionConfig 实战并行度、序列化、对象重用与全局参数
  • 家政类网站开发成本wordpress 音乐不中断
  • STM32-SPI协议
  • 西安网站开发php网站插件
  • LinearRAG—重新定义GraphRAG:无需关系抽取的线性图构建新范式 -香港理工
  • 第4章-程序计数器
  • HashMap 与 HashSet
  • 怎么在虚拟主机上建网站wordpress rest图片
  • 小米手机之间数据转移的6种方法
  • 前端开发中的表格标签
  • PaddleOCR-VL本地部署流程
  • 2.2 复合类型
  • 做网站图片自动切换宁波软件开发
  • quat:高性能四元数运算库
  • [MySQL]表——分组查询
  • 济南做网站的好公司有哪些极简资讯网站开发
  • 网站后台页面设计互联网+可以做什么项目
  • 项目八 使用postman实现简易防火墙功能
  • 使用postman 测试restful接口
  • 2008 iis 添加 网站 权限设置网站策划案4500
  • 以自主创新推动能源装备智能化升级,为能源安全构筑“确定性”底座
  • 构建AI智能体:七十六、深入浅出LoRA:低成本高效微调大模型的原理与实践
  • 中国各大网站排名网站源码爬取
  • FFmpeg 安装与配置教程(Windows 系统)
  • 【数字逻辑】 60进制数字秒表设计实战:用74HC161搭计数器+共阴极数码管显示(附完整接线图)
  • 新网网站空间花都网站建设价格
  • 前端底层原理与复杂问题映射表
  • Digital Micrograph下载安装教程
  • 怎么做网站的301建设设计院网站
  • 自己的服务器 linux centos8部署django项目