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

文章目录


前言

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

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


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

一、队列的基本概念

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

队列的特性

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

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

二、队列的实现方式

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

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

链式队列结构体

c 复制代码
typedef int QDataType; // 队列存储数据类型

typedef struct QueueNode {
    QDataType val;        // 存储的数据
    struct QueueNode *next; // 指向下一个节点的指针
} QueueNode;

typedef struct Queue {
    QueueNode *head;      // 队头指针
    QueueNode *tail;      // 队尾指针
} Queue;

初始化

c 复制代码
void QueueInit(Queue *pq) {
    pq->head = pq->tail = NULL;
}

入队操作

c 复制代码
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和tail
    if (pq->tail == NULL) {
        pq->head = pq->tail = newNode;
    } else {
        pq->tail->next = newNode;
        pq->tail = newNode;
    }
}

出队操作

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

销毁队列

c 复制代码
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) 普通数组队列

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

c 复制代码
// 普通数组队列示例
#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. 初始化
c 复制代码
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. 判空
c 复制代码
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
    return obj->front == obj->rear;
}
  1. 判满
c 复制代码
bool myCircularQueueIsFull(MyCircularQueue* obj) {
    return (obj->rear + 1) % (obj->k + 1) == obj->front;
}
  1. 入队操作
c 复制代码
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. 出队操作
c 复制代码
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
    if (myCircularQueueIsEmpty(obj)) {
        return false; // 队列为空
    }
    obj->front = (obj->front + 1) % (obj->k + 1); // front指针循环移动
    return true;
}
  1. 获取队头元素
c 复制代码
int myCircularQueueFront(MyCircularQueue* obj) {
    if (myCircularQueueIsEmpty(obj)) {
        // 通常返回一个特殊值或抛出异常
        return -1;
    }
    return obj->arr[obj->front];
}
  1. 获取队尾元素
c 复制代码
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. 销毁队列
c 复制代码
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. 获取队头元素

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

完整代码实现

c 复制代码
#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));  // 输出:1
    
    QueuePop(&q);  // 移除队头元素1
    
    QueuePush(&q, 3);
    QueuePush(&q, 4);
    
    // 依次出队并打印所有元素
    while (!QueueEmpty(&q))
    {
        printf("%d ", QueueFront(&q));  // 打印队头元素
        QueuePop(&q);                   // 移除队头元素
    }
    // 输出:2 3 4
    
    QueueDestroy(&q);
    
    return 0;
}

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

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

五、实际应用

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

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

六、注意事项

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

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

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

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

七、总结

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

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

相关推荐
为何创造硅基生物9 小时前
C语言 结构体内存对齐规则(通俗易懂版)
c语言·开发语言
仰泳之鹅9 小时前
【C语言】自定义数据类型2——联合体与枚举
c语言·开发语言·算法
jolimark10 小时前
C语言自学攻略:小白入门三步走
c语言·编程入门·学习路线·实践项目·自学攻略
cen__y11 小时前
Linux12(Git01)
linux·运维·服务器·c语言·开发语言·git
社交怪人11 小时前
【算平均分】信息学奥赛一本通C语言解法(题号2071)
c语言·开发语言
卢锡荣12 小时前
单芯通吃,盲插标杆 —— 乐得瑞 LDR6020,Type‑C 全场景互联 “智慧芯”
c语言·开发语言·计算机外设
Mr. zhihao12 小时前
深入解析redis基本数据结构
数据结构·数据库·redis
念何架构之路12 小时前
Go语言加密算法
数据结构·算法·哈希算法
AI科技星13 小时前
《数学公理体系·第三部·数术几何》(2026 年版)
c语言·开发语言·线性代数·算法·矩阵·量子计算·agi
失去的青春---夕阳下的奔跑13 小时前
560. 和为 K 的子数组
数据结构·算法·leetcode