一、队列的核心概念
队列(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) 入队 / 出队) |
| 适用场景 | 长度固定、访问频繁的场景 | 长度不确定、动态扩展的场景 |
七、总结
队列作为基础数据结构,核心是「先进先出」的特性,循环队列和链式队列是其两种典型实现方式:
- 循环队列通过数组 + 取模运算解决假溢出问题,适合长度固定的场景;
- 链式队列通过链表实现动态扩展,适合长度不确定的场景。