数据结构之队列

一、队列的核心概念

队列(Queue)是一种遵循先进先出(FIFO, First In First Out) 原则的线性数据结构,广泛应用于任务排队、消息处理、资源调度等场景。队列是只允许在一段进行插入,而在另一端进行删除操作的线性表。允许插入的称谓队尾,允许删除的一端队头。

队列的核心操作包括:

  • 入队(Enter):将元素添加到队列尾部;
  • 出队(Quit):从队列头部移除元素;
  • 判空(IsEmpty):判断队列是否为空;
  • 判满(IsFull):仅循环队列需要,判断队列是否已满;
  • 获取队头(GetHead):读取队列头部元素(不移除);
  • 销毁队列(Destroy):释放队列占用的内存。

根据底层存储结构,队列可分为循环队列(顺序存储)链式队列(链式存储),二者各有适用场景:

  • 循环队列:基于数组实现,访问效率高,但长度固定;
  • 链式队列:基于链表实现,长度动态扩展,但存在额外的指针开销。

二、数据类型定义(通用)

首先定义队列存储的通用数据类型(以任务调度为例),后续循环队列和链式队列均基于此类型实现:

复制代码
// 以任务调度为例,定义队列元素类型
typedef struct {
    char task_name[20];  // 任务名称
    int task_time;       // 任务耗时(秒)
} DATATYPE;

三、循环队列(顺序队列)的实现

3.1 循环队列的结构设计

循环队列基于数组实现,通过「取模运算」让数组首尾相连,避免普通顺序队列的「假溢出」问题。核心结构如下:

复制代码
// 循环队列结构定义
typedef struct {
    DATATYPE *array;  // 存储元素的数组
    int head;         // 队头指针(指向队头元素)
    int tail;         // 队尾指针(指向队尾下一个位置)
    int tlen;         // 队列总长度
} SeqQueue;

3.2 核心函数实现

(1)创建队列

初始化队列结构,分配数组内存,初始化队头、队尾指针:

复制代码
#include "seqque.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

SeqQueue *CreateSeqQueue(int len)
{
    SeqQueue* sq = (SeqQueue*)malloc(sizeof(SeqQueue));
    if(NULL == sq)
    {
        printf("CreateSeqQueue malloc1 error\n");
        return NULL;
    }

    sq->array = (DATATYPE*)malloc(sizeof(DATATYPE) * len);
    if(NULL == sq->array)
    {
        printf("CreateSeqQueue malloc2 error\n");
        free(sq); // 避免内存泄漏
        return NULL;
    }
    sq->head = 0;
    sq->tail = 0;
    sq->tlen = len;
    return sq;
}
(2)判空与判满
  • 判空:队头指针等于队尾指针;

  • 判满:(tail + 1) % tlen == head(预留一个空位,区分空和满)。

    // 判空
    int IsEmptySeqQueue(SeqQueue *queue)
    {
    return queue->head == queue->tail;
    }

    // 判满
    int IsFullSeqQueue(SeqQueue *queue)
    {
    return queue->head == (queue->tail + 1) % queue->tlen;
    }

(3)入队与出队
  • 入队:检查队列是否满,将元素拷贝到队尾,队尾指针取模后移;

  • 出队:检查队列是否空,队头指针取模后移(无需清空元素,覆盖即可)。

    // 入队
    int EnterSeqQueue(SeqQueue *queue, DATATYPE *data)
    {
    if(IsFullSeqQueue(queue))
    {
    printf("que is full\n");
    return 1;
    }
    memcpy(&queue->array[queue->tail], data, sizeof(DATATYPE));
    queue->tail = (queue->tail + 1) % queue->tlen;
    return 0;
    }

    // 出队
    int QuitSeqQueue(SeqQueue *queue)
    {
    if(IsEmptySeqQueue(queue))
    {
    printf("que is empty\n");
    return 1;
    }
    queue->head = (queue->head + 1) % queue->tlen;
    return 0;
    }

(4)获取队头与销毁队列
复制代码
// 获取队头元素(不移除)
DATATYPE* GetHeadSeqQue(SeqQueue *queue)
{
    if(IsEmptySeqQueue(queue))
    {
        printf("que is empty\n");
        return NULL;
    }
    return &queue->array[queue->head];
}

// 销毁队列(释放内存)
int DestroySeqQueue(SeqQueue *queue)
{
    free(queue->array);  // 释放数组内存
    free(queue);         // 释放队列结构内存
    return 0;
}

// 获取队列当前元素个数
int GetSizeSeqQue(SeqQueue *queue)
{
    if(queue->tail >= queue->head)
    {
        return queue->tail - queue->head;
    }
    else  
    {
        return queue->tail - queue->head + queue->tlen; // 修正原代码错误:无需+1
    }
}

四、链式队列的实现

4.1 链式队列的结构设计

链式队列基于单链表实现,包含头指针(指向队头)、尾指针(指向队尾)和元素个数,核心结构如下:

复制代码
// 链式队列节点结构
typedef struct LinkQueNode {
    DATATYPE data;          // 节点数据
    struct LinkQueNode *next; // 下一个节点指针
} LinkQueNode;

// 链式队列结构
typedef struct {
    LinkQueNode *head;  // 队头指针
    LinkQueNode *tail;  // 队尾指针
    int clen;           // 当前元素个数
} LinkQue;

4.2 核心函数实现

(1)创建队列

初始化队列结构,头、尾指针置空,元素个数置 0:

复制代码
#include "linkque.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

LinkQue *CreateLinkQue()
{
    LinkQue *lq = (LinkQue *)malloc(sizeof(LinkQue));
    if (NULL == lq)
    {
        printf("CreateLinkQue malloc error\n");
        return NULL;
    }
    lq->head = NULL;
    lq->tail = NULL;
    lq->clen = 0;
    return lq;
}
(2)入队

创建新节点,若队列为空则头、尾指针均指向新节点;否则将新节点链接到队尾,更新尾指针:

复制代码
int EnterLinkQue(LinkQue *lq, DATATYPE *newdata)
{
    LinkQueNode *newnode = (LinkQueNode *)malloc(sizeof(LinkQueNode));
    if (NULL == newnode)
    {
        printf("EnterLinkQue malloc error\n");
        return 1;
    }
    memcpy(&newnode->data, newdata, sizeof(DATATYPE));
    newnode->next = NULL;

    if(IsEmptyLinkQue(lq))
    {
        lq->head = newnode;
        lq->tail = newnode;
    }
    else  
    {
        lq->tail->next = newnode;
        lq->tail = newnode;
    }
    lq->clen++;
    return 0;
}
(3)出队与获取队头
复制代码
// 出队
int QuitLinkQue(LinkQue *lq)
{
    if (IsEmptyLinkQue(lq))
    {
        printf("que is empty\n");
        return 1;
    }
    LinkQueNode *tmp = lq->head;
    lq->head = lq->head->next;
    free(tmp);
    lq->clen--;
    // 若队列为空,尾指针置空
    if (lq->clen == 0) {
        lq->tail = NULL;
    }
    return 0;
}

// 获取队头元素(不移除)
DATATYPE *GetHeadLinkQue(LinkQue *lq)
{
    if(IsEmptyLinkQue(lq))
    {
        printf("que is empty\n");
        return NULL;
    }
    return &lq->head->data;
}
(4)判空、获取长度与销毁队列
复制代码
// 判空
int IsEmptyLinkQue(LinkQue *lq)
{
    return 0 == lq->clen;
}

// 获取元素个数
int GetSizeLinkQue(LinkQue *lq)
{
    return lq->clen;
}

// 销毁队列(释放所有节点和队列结构)
int DestroyLinkQue(LinkQue *lq)
{
    LinkQueNode* tmp = lq->head;
    while (tmp != NULL)
    {
        lq->head = lq->head->next;
        free(tmp);
        tmp = lq->head;
    }
    free(lq);
    lq = NULL; // 避免野指针
    return 0;
}

五、队列的实战应用:任务调度案例

以「老板下达任务,员工按顺序执行」为例,展示队列的完整使用流程(以循环队列为例,链式队列可直接替换使用)。

5.1 主函数实现

复制代码
#include <stdio.h>
#include <stdlib.h>
#include "seqque.h"
#include <unistd.h>
#include <string.h>

// 打印任务列表
void show_task(DATATYPE* data, int len)
{
    int i = 0;
    for (i = 0; i < len; i++)
    {
        printf("%d. %s %d秒\n", i, data[i].task_name, data[i].task_time);
    }
    return;
}

int main(int argc, char** argv)
{
    // 定义预设任务
    DATATYPE task[] = {
        {"cooking", 5},   // 0:做饭(5秒)
        {"washing", 8},   // 1:洗碗(8秒)
        {"studying", 3},  // 2:学习(3秒)
        {"over", 1}       // 3:结束任务
    };

    // 创建循环队列(容量10)
    SeqQueue* sq = CreateSeqQueue(10);
    if (sq == NULL) {
        return -1;
    }

    // 老板:展示任务列表,输入任务编号入队
    show_task(task, 4);
    int choose = 0;
    DATATYPE data = {0};
    while (1)
    {
        memset(&data, 0, sizeof(data));
        printf("请输入任务编号(0-3):");
        scanf("%d", &choose);
        if (choose >= 0 && choose <= 3)
        {
            strcpy(data.task_name, task[choose].task_name);
            data.task_time = task[choose].task_time;
            EnterSeqQueue(sq, &data);
        }
        else
        {
            printf("输入错误,请重新输入!\n");
            continue;
        }
        if (3 == choose)  // 输入3(over),停止添加任务
        {
            break;
        }
    }

    // 员工:按顺序执行队列中的任务
    printf("\n开始执行任务...\n");
    while(1)
    {
        DATATYPE* tmp = GetHeadSeqQue(sq);
        if(NULL == tmp) // 队列为空(异常)
        {
            break;
        }
        if(0 == strcmp(tmp->task_name, "over")) // 收到结束指令(正常退出)
        {
            break;
        }
        // 执行当前任务(每秒打印一次状态)
        while(tmp->task_time--)
        {
            printf("I'm %s....\n", tmp->task_name);
            sleep(1);
        }
        QuitSeqQueue(sq); // 任务执行完毕,出队
    }

    // 销毁队列
    DestroySeqQueue(sq);
    printf("\n所有任务执行完毕!\n");
    return 0;
}

5.2 运行效果

复制代码
0. cooking 5秒
1. washing 8秒
2. studying 3秒
3. over 1秒
请输入任务编号(0-3):0
请输入任务编号(0-3):2
请输入任务编号(0-3):3

开始执行任务...
I'm cooking....
I'm cooking....
I'm cooking....
I'm cooking....
I'm cooking....
I'm studying....
I'm studying....
I'm studying....

所有任务执行完毕!

六、循环队列 vs 链式队列

特性 循环队列 链式队列
存储结构 数组 单链表
长度限制 固定长度(创建时指定) 动态扩展(无长度限制)
内存开销 连续内存,无额外开销 每个节点含指针,有额外开销
访问效率 随机访问(O (1)) 顺序访问(O (1) 入队 / 出队)
适用场景 长度固定、访问频繁的场景 长度不确定、动态扩展的场景

七、总结

队列作为基础数据结构,核心是「先进先出」的特性,循环队列和链式队列是其两种典型实现方式:

  • 循环队列通过数组 + 取模运算解决假溢出问题,适合长度固定的场景;
  • 链式队列通过链表实现动态扩展,适合长度不确定的场景。
相关推荐
VekiSon1 小时前
数据结构与算法——树和哈希表
数据结构·算法
xu_yule2 小时前
数据结构与算法(1)(第一章复杂度知识点)(大O渐进表示法)
数据结构
大江东去浪淘尽千古风流人物3 小时前
【DSP】向量化操作的误差来源分析及其经典解决方案
linux·运维·人工智能·算法·vr·dsp开发·mr
fish_xk3 小时前
数据结构之排序
数据结构
sinat_602035363 小时前
翁恺 6.3.1逻辑运算-函数
c语言
Unstoppable223 小时前
代码随想录算法训练营第 56 天 | 拓扑排序精讲、Dijkstra(朴素版)精讲
java·数据结构·算法·
potato_may3 小时前
CC++ 内存管理 —— 程序的“五脏六腑”在哪里?
c语言·开发语言·数据结构·c++·内存·内存管理
饕餮怪程序猿3 小时前
A*算法(C++实现)
开发语言·c++·算法
电饭叔3 小时前
不含Luhn算法《python语言程序设计》2018版--第8章14题利用字符串输入作为一个信用卡号之二(识别卡号有效)
java·python·算法