一、队列

(1) 、基本概念

  • 概念:队列是最常见的概念,日常生活经常需要排队,仔细观察队列会发现,队列是一种逻辑结构,是一种特殊的线性表,特殊在只能在固定的两端操作线性表,只要满足上述特性,那么这种特殊的线性表就会呈现一种"先进先出"的逻辑,这种逻辑就被称为队列
  • 图解

  • 说明1:从上述动图中可以观察到,需要牺牲至少数组中的一个存储位置,来区分队列中的满队和空队
  • 说明2:由于约定了只能在线性表固定的两端进行操作,于是给队列这种特殊的线性表的插入删除,起了个特殊的名称:
    • 队头:可以删除节点的一端
    • 队尾:可以插入节点的一端
    • 入队:将节点插入到队尾之后,函数名通常为EnQueue() --- 增加数据
    • 出队:将队头节点从队列中删除,函数名通常为OutQueue() --- 删除数据
    • 取队头:取得队头元素,但不出队,函数名通常为front() --- 查改数据

(2) 、队列的储存方式

  • 说明:与其他的逻辑结构类似,队列可以采用顺序存储形成的循环队列,也可以采用链式存储形成的链式队列。顺序存储的队列之所以被称为循环队列,是因为可以利用更新队头队尾的下标信息,来循环地利用整个数组,出队入队时也不必移动当中地数据

(3)、顺序队列的基本操作

1、顺序队列的管理结构体设计

  • 说明:顺序队列通常使用数组实现,核心结构体包含三个关键成员:一个固定大小的数组存储元素,两个整型变量分别记录队头(front)和队尾(rear)位置。数组大小需预先定义或动态分配。
  • 图解

  • 示例代码
// 顺序队列的管理结构体
typedef struct sequential_queue
{
    datatype_p data_p;  // 指向顺序队列内存的指针
    int capacity;       // 顺序队列的容量
    int front;          // 顺序队列的队头的元素下标
    int rear;           // 顺序队列的队尾的元素下标

}sq_queue_t, *sq_queue_p;

2、初始化顺序队列

  • 说明:顺序队列是一种基于数组实现的队列结构,通过固定大小的连续内存空间存储元素,包含队头(front)和队尾(rear)指针。初始化时需分配数组空间,并将 front 和 rear 均置为 -1 或 0(视实现方式而定),表示队列为空。
  • 图解:

  • 示例代码
/**
 * @brief: 初始化顺序队列
 * @note:  None
 * @param: cap_size:   顺序队列的容量
 * @retval: 成功:返回指向这个顺序队列的内存的指针
 *          失败:返回NULL
*/
sq_queue_p SEQUENTIAL_QUEUE_Init(int cap_size)
{
    // 1、申请顺序队列的堆内存空间
    sq_queue_p p = malloc(sizeof(sq_queue_t));
    bzero(p, sizeof(sq_queue_t));

    // 2、给该内存空间(堆区1)进行赋值操作
    if (p!=NULL)
    {
        // 指向顺序队列内存(堆区2)的指针
        p->data_p = malloc(sizeof(datatype)*cap_size);
        if (p->data_p == NULL)
        {
            free(p);
            return NULL;
        }

        // 顺序队列的容量
        p->capacity = cap_size;

        // 顺序队列的队头的元素下标
        p->front = 0;

        // 顺序队列的队尾的元素下标
        p->rear = 0;
    }
    else
    {
        return NULL;
    }
    
    // 3、成功返回指向这个顺序队列管理结构体的内存指针
    return p;

}

3、判断顺序队列是否为空

  • 说明:顺序队列通常由数组实现,需要维护队头(front)和队尾(rear)两个指针。队列为空的条件是队头指针和队尾指针指向同一位置。
  • 图解:

  • 示例代码
/**
 * @brief: 判断顺序队列是否为空
 * @note:  None
 * @param: p:    指向这个顺序队列的内存的指针
 * @retval: 如果顺序队列为空:返回true
 *          如果顺序队列非空:返回false
*/
bool SEQUENTIAL_QUEUE_IfEmpty(sq_queue_p p)
{
    return (p->front == p->rear);
}

4、判断顺序队列是否满了

  • 说明:检查顺序队列的队尾指针 rear 是否等于队列的最大容量减一(基于数组索引从0开始)。若 rear == capacity - 1或判断 (rear + 1) % capacity == front,则队列已满。
  • 图解

  • 示例代码
/**
 * @brief: 判断顺序队列是否为满
 * @note:  None
 * @param: p:    指向这个顺序队列的内存的指针
 * @retval: 如果顺序队列为满:返回true
 *          如果顺序队列非满:返回false
*/
bool SEQUENTIAL_QUEUE_IfFull(sq_queue_p p)
{
    return ((p->rear+1)%(p->capacity) == p->front);
}

5、入队(增加数据)

  • 说明:入队是队列数据结构中的核心操作,用于在队列的尾部添加新元素。队列遵循先进先出原则,即最早入队的元素会最先被移除。
  • 图解

  • 示例代码:
/**
 * @brief: 入队 --- 增加数据
 * @note:  None
 * @param: p:    指向这个顺序队列的内存的指针
 *          data:要入队的数据
 * @retval: 成功:返回0
 *          失败:返回-1
*/
int SEQUENTIAL_QUEUE_EnQueue(sq_queue_p p, datatype data)
{
    // 1、判断队列是否是满的
    if (SEQUENTIAL_QUEUE_IfFull(p))
        return -1;

    // 2、在队列的队尾插入数据
    p->data_p[p->rear] = data;

    // 3、队尾标志+1
    p->rear = (p->rear+1)%(p->capacity);    // 因为是循环队列,因此需要取余操作
    
    // 4、成功返回0
    return 0;
}

6、出队(删除数据)

  • 说明:出队是队列的基本操作之一,用于删除队列的队首元素并返回该元素。队列遵循原则,因此出队操作总是针对最早进入队列的数据。
  • 图解

  • 示例代码
/**
 * @brief: 出队 --- 删除数据
 * @note:  None
 * @param: p:    指向这个顺序队列管理结构体内存的指针
 * @retval: 成功:返回0
 *          失败:返回-1
*/
int SEQUENTIAL_QUEUE_OutQueue(sq_queue_p p)
{
    // 1、判断队列是否是空的
    if (SEQUENTIAL_QUEUE_IfEmpty(p))
        return -1;

    // 2、在队列的队头删除数据
    bzero(&p->data_p[p->front], sizeof(datatype));  // 清空数据(这一步可以不用,看你的)
    p->front = (p->front+1)%(p->capacity);          // 因为是循环队列,因此需要取余操作,控制其范围

    // 3、成功返回0
    return 0;
}

7、遍历队列

  • 说明:队列是一种先进先出的数据结构,遍历队列通常指按顺序访问队列中的所有元素
  • 图解

  • 示例代码
/**
 * @brief: 遍历队列数据 --- 查找数据
 * @note:  None
 * @param: p:    指向这个顺序队列管理结构体内存的指针
 * @retval: 成功:返回0
 *          失败:返回-1
*/
int SEQUENTIAL_QUEUE_Show(sq_queue_p p)
{
    if (SEQUENTIAL_QUEUE_IfEmpty(p))
        return -1;

    // 遍历整个顺序队列
    printf("=====================顺序栈里面的数据======================\n");
    for (int i = p->front; i!=p->rear; i=(i+1)%(p->capacity))
    {            
        printf("顺序队列里面的第[%d]个数据为 == %d\n", i, p->data_p[i].data);
    }

    return 0;
}

8、获取队头的数据 --- 查找数据

  • 说明:队列是一种先进先出的数据结构,队头数据是第一个被插入的元素,也是第一个被访问或删除的元素。
  • 图解

  • 示例代码
/**
 * @brief: 获取队头的数据 --- 查找数据
 * @note:  None
 * @param: p:    指向这个顺序队列管理结构体内存的指针
 * @retval: 成功:返回队头的数据
 *          失败:返回NULL
*/
datatype_p SEQUENTIAL_QUEUE_GetFrontData(sq_queue_p p)
{
    if (SEQUENTIAL_QUEUE_IfEmpty(p))
        return NULL;
    
    // 将队头的数据返回
    return &(p->data_p[p->front]);
}

9、销毁队列

  • 说明:通过循环逐个取出队列中的元素,直到队列为空。
  • 图解

  • 示例代码
/**
 * @brief: 销毁队列
 * @note:  None
 * @param: p:    指向这个顺序队列管理结构体内存的指针
 * @retval: None
*/
void SEQUENTIAL_QUEUE_UnInit(sq_queue_p p)
{
    free(p->data_p);    // 先释放堆区2的资源
    free(p);            // 再释放堆区1的资源
}

10、顺序队列的使用

/**
  ******************************************************************************
  * @file    main.c
  * @author  MChine慕青
  * @version V0.0.1
  * @date    2025.09.22
  * @brief   使用顺序队列实现数据的增删查改功能
  *          环境:ubuntu22.04
  *          编译:gcc main.c sequential_queue.c
  *          执行:./a.out
  *      
  ******************************************************************************
  * @attention
  *
  *  本文档只供学习使用,不得商用,违者必究
  * 
  *  个人博客:     https://blog.csdn.net/2402_83622972?type=blog
  *  有疑问或者建议:3211735057@qq.com
  * 
  ******************************************************************************
  */

#include "sequential_queue.h"

int main(int argc, char const *argv[])
{
    // (1)、初始化顺序队列
    sq_queue_p p = SEQUENTIAL_QUEUE_Init(10);
    if (p == NULL)
    {
        printf("初始化顺序队列失败!\n");
        return -1;
    }

    // (2)、选择功能(增删查改、退出(销毁顺序队列))
    int select           = 0;
    datatype new_data    = {0};
    datatype_p get_data  = NULL;  
    datatype change_data = {0};  

    while (1)
    {
        // 1、显示整个顺序队列的数据
        SEQUENTIAL_QUEUE_Show(p);

        // 2、显示功能选项
        printf("请选择以下功能!\n");
        printf("1、入队(增加数据)\n");
        printf("2、出队(删除数据)\n");
        printf("3、取队头数据\n");
        printf("4、修改队头数据\n");
        printf("5、退出!\n");

        // 3、选择要做的功能
        scanf("%d", &select);
        while(getchar()!='\n');

        switch (select)
        {
            case 1:
                // 提示
                printf("请输入要插入的数据:\n");

                // 输入数据
                scanf("%d", &new_data.data);
                while(getchar()!='\n');

                // 添加数据到顺序队列中
                SEQUENTIAL_QUEUE_EnQueue(p, new_data);

                break;
            
            case 2:
                // 删除顺序队列中的数据(队头)
                SEQUENTIAL_QUEUE_OutQueue(p);
                break;

            case 3:
                // 提示
                printf("正在获取队头数据\n");

                // 获取队头的数据
                get_data =  SEQUENTIAL_QUEUE_GetFrontData(p);
                if (get_data != NULL)
                    printf("get_data.data == %d\n", get_data->data);
                
                break;

            case 4:
                // 提示
                printf("请输入要修改的队头数据\n");

                // 输入数据
                scanf("%d", &change_data.data);
                while(getchar()!='\n');

                // 修改队头的数据
                SEQUENTIAL_QUEUE_ChangeFrontData(p, change_data);
                break;

           case 5:
                SEQUENTIAL_QUEUE_UnInit(p);
                printf("系统已退出!\n");
                goto exit_sys_label;
                break;
        }
    }
exit_sys_label:
    return 0;
}

(4)、链式队列的基本操作

1、链式队列的管理结构体设计

  • 说明:链式队列通常由两个指针(frontrear)和一个节点结构体组成。front指向队首节点,rear指向队尾节点,节点结构体包含数据域和指向下一节点的指针。
  • 图解

  • 示例代码

// 节点设计
typedef struct node
{
    // 数据域
	datatype data;

	// 指针域
	struct node *next_p;
}node_t, *node_p;

// 链式队列管理结构体设计
typedef struct link_cir_queue
{
   // 链式队列的队头指针
   node_p front_p;

   // 链式队列的队尾指针
   node_p rear_p;

   // 链式队列的当前的元素个数
   int num;

}lc_queue_t, *lc_queue_p;

2、初始化链式队列

  • 说明:链式队列通过动态分配内存创建头节点,并设置队头(front)和队尾(rear)指针指向该节点。头节点的next指针初始化为NULL,表示队列为空。
  • 图解

  • 示例代码
/**
 * @brief: 初始化链式队列管理结构体(里面有初始化头节点)
 * @note:  None
 * @param: None
 * @retval: 成功:返回指向这个链式队列管理结构体内存的指针
 *          失败:返回NULL
*/
lc_queue_p LINK_CIR_QUEUE_Init(void)
{
    // 1、申请管理结构体堆内存空间
    lc_queue_p p = malloc(sizeof(lc_queue_t));
    bzero(p, sizeof(lc_queue_t));

    // 2、申请成功,给堆内存空间进行赋值
    if ( p != NULL )
    {
        // a、申请头节点内存
        node_p head_node = malloc(sizeof(node_t));
        bzero(head_node, sizeof(node_t));
        if (head_node != NULL)
        {
            // 数据域

            // 指针域
            head_node->next_p = head_node;
        }
        else
        {
            free(p);
            return NULL;
        }
        
        // b、队头指针
        p->front_p = head_node;

        // c、队尾指针
        p->rear_p  = head_node;

        // d、链式队列当前元素个数
        p->num    = 0;

    }
    else
    {
        return NULL;
    }

    // 3、成功返回指向链式队列管理结构体的指针
    return p;
    
}

3、初始化数据节点

  • 说明:队列是一种先进先出(FIFO)的线性数据结构,初始化数据节点是构建队列的基础操作。数据节点通常包含存储的数据和指向下一个节点的指针。
  • 图解

  • 示例代码
/**
 * @brief: 初始化数据节点
 * @note:  None
 * @param: data:要赋值的数据
 * @retval: 成功:返回指向这个数据节点的指针
 *          失败:返回NULL
*/
node_p LINK_CIR_QUEUE_InitDataNode(datatype data)
{
    // 1、给数据节点申请堆内存空间
    node_p p = malloc(sizeof(node_t));
    bzero(p, sizeof(node_t));

    // 2、给堆内存空间赋值
    if (p!=NULL)
    {
        // 数据域
        p->data = data;

        // 指针域
        p->next_p = p;
    }

    // 3、成功返回指向这个数据节点的指针
    return p;
}

4、判断队列是否为空

  • 说明:循环队列为空的条件是队头指针(front)等于队尾指针(rear)。此时队列中没有元素,两个指针指向同一位置。
  • 图解

  • 示例代码
/**
 * @brief: 判断链式队列是否为空
 * @note:  None
 * @param: p: 指向链式队列管理结构体的指针
 * @retval: 如果链式队列为空:返回true
 *          如果链式队列非空:返回false
*/
bool LINK_CIR_QUEUE_IfEmpty(lc_queue_p p)
{ 
    // 方法一:
    // 管理结构体里面的front_p
    node_p head_node = p->front_p;
    return head_node->next_p == head_node;

    /*
        // 方法二:
        // 管理结构体里面的rear_p
        node_p head_node = p->rear_p;
        return head_node->next_p == head_node;
    */

    /*
        // 方法三:利用管理结构体里面的num
        return p->num == 0
    */
}

5、入队(增加数据)

  • 说明:链式队列的入队操作需动态申请新节点,存储数据后插入队尾;创建新节点,存入待插入数据,其next指针置空。若队列为空,新节点同时作为队头和队尾。若队列非空,将队尾节点的next指向新节点,并更新队尾指针。
  • 图解 

  • 示例代码
/**
 * @brief: 入队 --- 插入数据(尾插法)
 * @note:  None
 * @param: p:       指向链式队列管理结构体的指针
 *          new_node:要插入的数据节点
 * @retval: None
*/
void LINK_CIR_QUEUE_EnQueue(lc_queue_p p,  node_p new_node)
{
    // 让队头指针赋值为head_node
    node_p head_node = p->front_p;

    // 1、让p->rear_p的next_p指向新节点
    p->rear_p->next_p = new_node;

    // 2、再让new_node的next_p指向head_node
    new_node->next_p = head_node;

    // 3、让p->rear_p指向最后一个数据节点
    p->rear_p = new_node;

    // 4、将管理结构体的num+1
    p->num++;
}

6、出队(删除数据)

  • 说明:队列的出队操作遵循先进先出(FIFO)原则,从队列前端移除元素。在数组实现中,移动队首指针;链表实现中,调整头节点指针。若队列为空,则触发下溢错误。
  • 图解

  • 示例代码
/**
 * @brief: 出队 --- 删除数据
 * @note:  None
 * @param: p: 指向链式队列管理结构体的指针
 * @retval: 成功:返回0
 *          失败:返回-1
*/
int LINK_CIR_QUEUE_OutQueue(lc_queue_p p)
{
    // 1、判断链式队列是否为空,是的话,返回-1
    if (LINK_CIR_QUEUE_IfEmpty(p))
        return -1;

    // 管理结构体里面的队头指针给赋值为head_node
    node_p head_node = p->front_p;

    // 2、相关中间变量赋值
    node_p last_node = head_node;
    node_p del_node  = head_node->next_p;
    node_p next_node = del_node->next_p;

    // 3、绕过原链表要删除的节点
    last_node->next_p = next_node;
    
    // 4、释放要删除节点的资源
    del_node->next_p  = NULL;
    free(del_node);
    
    // 5、管理结构体中的num需要-1
    p->num--;

    // 6、删除到链式栈最后一个数据节点时,需要将rear_p指针指向头节点
    if (p->num == 0)
    {
        p->rear_p = p->front_p;
    }
    
    // 6、成功返回0
    return 0;
}

7、遍历数据

  • 说明:从头到尾遍历一遍,将数据打印输出出来即可
  • 图解

  • 示例代码
/**
 * @brief: 遍历整个链式队列并打印出里面的数据
 * @note:  None
 * @param: p: 指向链式队列管理结构体的指针
 * @retval: 成功:返回0
 *          失败:返回-1
*/
int LINK_CIR_QUEUE_Show(lc_queue_p p)
{
    // 1、判断链式栈是否为空,是的话,返回-1
    if (LINK_CIR_QUEUE_IfEmpty(p))
        return -1;   

    // 2、遍历整个链式栈,并逐个打印里面的数据
    node_p tmp_p = NULL;
    int i = 0;

    printf("=====================链式栈中的数据===================\n");
    for (tmp_p = p->front_p->next_p; tmp_p!=p->rear_p->next_p; tmp_p=tmp_p->next_p)
    {
        printf("链式栈中的第%d的节点,数据为:%d\n", i, tmp_p->data.data);
        i++;
    }
    printf("====================================================\n");

    // 3、成功返回0
    return 0; 
}

8、获取和修改队头数据

  • 说明:在循环队列中,队头数据通常存储在front指针指向的位置。若队列非空,直接返回该位置的元素即可。
  • 图解

  • 示例代码
/**
 * @brief: 获取队头数据
 * @note:  None
 * @param: p:   指向链式队列管理结构体的指针
 * @retval: 成功:返回指向队列队头的数据节点的指针
 *          失败:返回NULL
*/
datatype_p LINK_CIR_QUEUE_GetFrontData(lc_queue_p p)
{
    // 1、判断链式队列是否为空,是的话,返回-1
    if (LINK_CIR_QUEUE_IfEmpty(p))
        return NULL;

    // 将管理结构体里面的front_p指针进行赋值
    node_p head_node = p->front_p;

    // 2、获取栈顶数据
    return &(head_node->next_p->data);
}

/**
 * @brief: 修改队头数据
 * @note:  None
 * @param: p:   指向链式队列管理结构体的指针
 *          data: 要修改的数据
 * @retval: 成功:返回0
 *          失败:返回-1
*/
int LINK_CIR_QUEUE_ChangeFrontData(lc_queue_p p, datatype data)
{
    // 1、判断链式队列是否为空,是的话,返回-1
    if (LINK_CIR_QUEUE_IfEmpty(p))
        return -1;

     // 将管理结构体里面的front_p指针进行赋值
    node_p head_node = p->front_p;

    // 2、修改队头数据
    head_node->next_p->data = data;

    // 3、成功返回0
    return 0;
}

9、销毁队列

  • 说明:

    在循环队列中,队头数据通常存储在front指针指向的位置。若队列非空,直接返回该位置的元素即可。

  • 图解

  • 示例代码
/**
 * @brief: 销毁链式队列
 * @note:  None
 * @param: p: 指向链式队列管理结构体的指针
 * @retval: None
*/
void LINK_CIR_QUEUE_UnInit(lc_queue_p p)
{
    // 1、如果链式队列为空,那么直接释放管理结构体内存即可
    if (LINK_CIR_QUEUE_IfEmpty(p))
    {
        free(p->front_p); // 释放堆区2(释放头节点数据)
        free(p);          // 释放堆区1
        return;
    }

    // 将管理结构体里面的front_p指针进行赋值
    node_p head_node = p->front_p;

    // 2、销毁链式栈中的数据节点
    node_p tmp_p  = NULL;
    node_p tmp2_p = NULL;
    for (tmp_p=head_node->next_p; tmp_p!=head_node; tmp_p=tmp2_p)
    {
        tmp2_p = tmp_p->next_p;
        free(tmp_p);
    }
    
    // 3、释放堆区2(释放头节点数据)
    free(head_node);

    // 4、释放堆区1
    free(p);        
}

10、循环队列的使用

/**
  ******************************************************************************
  * @file    main.c
  * @author  R.慕青
  * @version V0.0.1
  * @date    2025.09.25
  * @brief   使用链式队列实现数据的增删查改功能
  *          前提:需要设置link_cir_queue.h里面的参数
  *          环境:ubuntu16.04
  *          编译:gcc main.c link_cir_queue.c
  *          执行:./a.out
  *      
  ******************************************************************************
  * @attention
  *
  *  本文档只供学习使用,不得商用,违者必究
  * 
  *  个人博客网:    https://blog.csdn.net/2402_83622972?spm=1010.2135.3001.5343  
  *  有疑问或者建议:3211735057@qq.com
  * 
  ******************************************************************************
  */
#include "link_cir_queue.h"

int main(int argc, char const *argv[])
{
    // (1)、初始化管理结构体
    lc_queue_p p = LINK_CIR_QUEUE_Init();
    if ( p == NULL)
    {
        printf("初始化链式队列管理结构体失败!\n");
        return -1;
    }

    // (2)、选择功能(增删查改、退出(销毁链式队列))
    node_p new_node        = NULL;
    int select             = 0;
    datatype   new_data    = {0};
    datatype_p get_data    = NULL;
    datatype   change_data = {0};

    while (1)
    {
        // 1、显示整个链式队列的数据
        LINK_CIR_QUEUE_Show(p);

        // 2、显示功能选项
        printf("请输入以下功能:\n");
        printf("1、入队(增加数据)\n");
        printf("2、出队(删除数据)\n");
        printf("3、获取队头数据\n");
        printf("4、修改队头数据\n");
        printf("5、退出!\n");

        // 3、选择要做的功能
        scanf("%d", &select);
        while(getchar()!='\n');

        switch (select)
        {
            case 1:
                // 提示
                printf("请输入要入队的数据:\n");

                // 输入数据
                scanf("%d", &new_data.data);
                while(getchar()!='\n');

                // 生成一个数据节点
                new_node = LINK_CIR_QUEUE_InitDataNode(new_data);

                // 将新生成的数据节点添加到链式队列中
                LINK_CIR_QUEUE_EnQueue(p, new_node);

                break;
        
            
            case 2:
                // 删除链式队列中的数据(队头)
                LINK_CIR_QUEUE_OutQueue(p);
                break;

            case 3:
                // 提示
                printf("正在获取队头数据\n");

                // 获取队头数据
                get_data =  LINK_CIR_QUEUE_GetFrontData(p);
                if (get_data != NULL)
                    printf("get_data.data == %d\n", get_data->data);
                
                break;

            case 4:
                // 提示
                printf("请输入要修改的队头数据\n");

                // 输入数据
                scanf("%d", &change_data.data);
                while(getchar()!='\n');

                // 修改队头的数据
                LINK_CIR_QUEUE_ChangeFrontData(p, change_data);
                break;

           case 5:
                LINK_CIR_QUEUE_UnInit(p);
                printf("系统已退出!\n");
                goto exit_sys_label;
                break;
        }
}
exit_sys_label: 
    return 0;
}

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐