数据结构:队列篇
图均为手绘,代码基于vs2022实现
系列文章目录
数据结构初探: 顺序表
数据结构初探:链表之单链表篇
数据结构初探:链表之双向链表篇
链表特别篇:链表经典算法问题
数据结构:栈篇
文章目录
- 系列文章目录
- 前言
- 一.队列的概念和结构
- 1.1概念
- 一、动态内存管理优势
- 二、操作效率与安全性
- 1.2结构
- 二.准备工作
- 1.Queue.h:
- 2.Queue.c:
- 3.test.c:
- 三.队列的数据操作的实现(增删查改)
- 1.Queue.h:
- 2.Queue.c:
- 2.1队列的初始化;
- 2.2队列的销毁
- 2.3队列节点的创建
- 2.4队列的插入(入队)
- 2.5队列的删除(出队)
- 2.6返回元素个数
- 2.7判断队列为空
- 2.8返回队列的队头元素,即要出队的元素
- 2.9返回队列的队尾元素,即入队的元素
- 2.10完整代码
- 3.test.c
- 四.队列的优缺点
- **队列的优点**
- **队列的缺点**
- **实际应用中的取舍建议**
- 总结
前言
在计算机科学的世界中,高效管理"先来后到"的秩序往往能解决许多复杂问题。无论是操作系统调度任务、网络请求排队处理,还是生活中常见的点餐叫号系统,背后都离不开一个看似简单却至关重要的数据结构——队列(Queue)。
队列的核心理念如同我们日常生活中的排队规则:先到者先服务(First In, First Out,即FIFO)。这种看似朴素的思想,却在算法设计、系统架构甚至高并发场景中扮演着关键角色。它既可以是算法题中巧妙的解题工具,也能成为分布式系统中缓解流量洪峰的利器。
本文将带您深入队列的运作机制:从基础概念到实际应用,从手写实现到性能优化,我们不仅会剖析队列的「先进先出」特性,还会探讨循环队列进阶形态。无论您是初探数据结构的新手,还是希望温故知新的开发者,相信都能通过本文重新发现队列的独特魅力。
一.队列的概念和结构
1.1概念
队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out)
入队列:进行插入操作的一端称为队尾 (rear)
出队列:进行删除操作的一端称为队头(front)
- 队列既可以用数组实现,也可以用链表实现,但是在一般情况下,我们会选择使用链表进行实现
但是为什么呢?
一、动态内存管理优势
-
按需扩容
链表节点动态分配内存,队列长度无需预先声明上限。当数据规模不可预知时(如网络请求队列),链表可避免数组的「空间预分配浪费」或「频繁扩容」问题。 -
避免假溢出问题
数组实现的循环队列需要预留一个空位判断队满,实际可用空间为MAX_SIZE-1
,而链表天然支持动态增长,无此限制。但是在学习中我们使用数组实现循环队列会使得逻辑更加清晰明了,为了更好理解,我会在<<栈和队列特别篇:经典算法问题>>中为大家讲解其中的好处
二、操作效率与安全性
-
稳定的时间复杂度
链表的入队(O(1)
)和出队(O(1)
)操作无需像动态数组那样触发内存拷贝,性能可预测性更强。 -
内存碎片化更低
频繁的数组扩容/缩容可能产生内存碎片,而链表的节点分散存储能更灵活利用内存空隙。 -
无数据搬迁开销
数组出队时,若采用「非循环」结构需移动元素;链表只需修改指针指向,无数据搬移成本。
1.2结构
整体图结构如图:
首先我们是以链表实现,所以我们需要节点结构体:
//队列节点的创建;
typedef struct QueueNode
{QDataType data;//存储数据struct QueueNode* next;//下一个节点地址
}QNode;
队列的创建:
//队列的传参节点;队列的结构;
typedef struct Queue
{QNode* head;//队头QNode* tail;//队尾int size;//有效数据个数
}Queue;
上面这种方式可以减少我们传参过多导致混乱的问题;
让我们继续来实现队列的数据操作;
二.准备工作
创建对应的三个文件夹:
1.Queue.h:
用于存储顺序表的结构和增删查改函数声明,以及对应的库函数;
2.Queue.c:
用于函数的实现;
3.test.c:
用于测试和修改;
ps:2和3,均要包含头文件1,即(#include"Queue.h").
三.队列的数据操作的实现(增删查改)
1.Queue.h:
#pragma once#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>typedef int QDataType;
//队列节点的创建;
typedef struct QueueNode
{QDataType data;struct QueueNode* next;
}QNode;
//队列的传参节点;队列的结构;
typedef struct Queue
{QNode* head;QNode* tail;int size;
}Queue;
//初始化
void QueueInit(Queue* pq);
//销毁
void QueueDestroy(Queue* pq);
//创建新节点
QNode* CreateNode(QDataType x);
//插入
void QueuePush(Queue* pq, QDataType x);
//删除
void QueuePop(Queue* pq);
//返回有效数据个数
int QueueSize(Queue* pq);
//判空
bool QueueEmpty(Queue* pq);
//返回头元素
QDataType QueueFront(Queue* pq);
//返回尾元素
QDataType QueueBack(Queue* pq);
如何实现呢,我们往下看;
2.Queue.c:
2.1队列的初始化;
//还是老问题,修改结构体内部变量,一级指针即可
void QueueInit(Queue* pq)
{assert(pq);//防止传空pq->head = pq->tail = NULL;//全部初始化为空,即空队列pq->size = 0;
}
2.2队列的销毁
void QueueDestroy(Queue* pq)
{assert(pq);//从头开始QNode* cur = pq->head;while (cur)//不断往下释放,直到全部销毁{QNode* next = cur->next;free(cur);cur = next;}pq->head = pq->tail = NULL;//恢复至初始化pq->size = 0;
}
2.3队列节点的创建
QNode* CreateNode(QDataType x)
{//动态开辟空间分配QNode* newNode = (QNode*)malloc(sizeof(QNode));if (newNode == NULL)//判断是否开辟成功{perror("malloc fail");return NULL;}newNode->data = x;//存储数据newNode->next = NULL;//下一个节点指向置空
//方便多次复用return newNode;//返回
}
2.4队列的插入(入队)
void QueuePush(Queue* pq, QDataType x)
{assert(pq);QNode* newNode = CreateNode(x);if (pq->head == NULL)//头尾都为空才能说明真的为空{assert(pq->tail == NULL);pq->head = pq->tail = newNode;//头尾都指向新节点}else//其他情况{pq->tail->next = newNode;//在尾节点后链接pq->tail = newNode;//更新尾}pq->size++;//有效数据个数++,更新个数;
}
2.5队列的删除(出队)
void QueuePop(Queue* pq)
{assert(pq);//assert(!QueueEmpty(pq));assert(pq->head != NULL);//这里有两种代码逻辑://一.//QNode* next = pq->head->next;//free(pq->head);//pq->head = next;//if (pq->head == NULL)//{// pq->tail = NULL;//}//二.if (pq->head->next == NULL)//如果只有一个节点{free(pq->head);//直接释放并且归零pq->head = pq->tail = NULL;}else{//有多个节点,则记录节点下一个,释放节点QNode* next = pq->head->next;free(pq->head);pq->head = next;}pq->size--;//更新有效数据个数
}
2.6返回元素个数
int QueueSize(Queue* pq)
{assert(pq);return pq->size;
}
2.7判断队列为空
bool QueueEmpty(Queue* pq)
{assert(pq);return pq->size == 0;//用size判断是否为空,要保证size不出问题;//return pq->head == NULL && pq->tail == NULL;
}
2.8返回队列的队头元素,即要出队的元素
QDataType QueueFront(Queue* pq)
{assert(pq);assert(!QueueEmpty(pq));return pq->head->data;
}
2.9返回队列的队尾元素,即入队的元素
QDataType QueueBack(Queue* pq)
{assert(pq);assert(!QueueEmpty(pq));return pq->tail->data;
}
2.10完整代码
#define _CRT_SECURE_NO_WARNINGS 1#include"Queue.h"//队列的初始化;
void QueueInit(Queue* pq)
{assert(pq);pq->head = pq->tail = NULL;pq->size = 0;
}//队列的销毁;
void QueueDestroy(Queue* pq)
{assert(pq);QNode* cur = pq->head;while (cur){QNode* next = cur->next;free(cur);cur = next;}pq->head = pq->tail = NULL;pq->size = 0;
}//队列节点的创建;
QNode* CreateNode(QDataType x)
{QNode* newNode = (QNode*)malloc(sizeof(QNode));if (newNode == NULL){perror("malloc fail");return NULL;}newNode->data = x;newNode->next = NULL;return newNode;
}//队列的插入(入队);
void QueuePush(Queue* pq, QDataType x)
{assert(pq);QNode* newNode = CreateNode(x);if (pq->head == NULL){assert(pq->tail == NULL);pq->head = pq->tail = newNode;}else{pq->tail->next = newNode;pq->tail = newNode;}pq->size++;
}//队列的删除(出队);
void QueuePop(Queue* pq)
{assert(pq);//assert(!QueueEmpty(pq));assert(pq->head != NULL);//QNode* next = pq->head->next;//free(pq->head);//pq->head = next;//if (pq->head == NULL)//{// pq->tail = NULL;//}if (pq->head->next == NULL){free(pq->head);pq->head = pq->tail = NULL;}else{QNode* next = pq->head->next;free(pq->head);pq->head = next;}pq->size--;
}//返回元素个数;
int QueueSize(Queue* pq)
{assert(pq);return pq->size;
}//判断队列为空;
bool QueueEmpty(Queue* pq)
{assert(pq);return pq->size == 0;//用size判断是否为空,要保证size不出问题;//return pq->head == NULL && pq->tail == NULL;
}//返回队列的队头元素,即要出队的元素;
QDataType QueueFront(Queue* pq)
{assert(pq);assert(!QueueEmpty(pq));return pq->head->data;
}//返回队列的队尾元素,即入队的元素;
QDataType QueueBack(Queue* pq)
{assert(pq);assert(!QueueEmpty(pq));return pq->tail->data;
}
我们现在已经学会如何对数据进行在队列中的操作了,让我们来测试一下
3.test.c
#define _CRT_SECURE_NO_WARNINGS 1#include"Queue.h"int main()
{Queue q;QueueInit(&q);QueuePush(&q, 1);QueuePush(&q, 2);QueuePush(&q, 3);QueuePush(&q, 4);QueuePush(&q, 5);while (!QueueEmpty(&q)){printf("%d ", QueueFront(&q));QueuePop(&q);}printf("\n");QueueDestroy(&q);return 0;
}
四.队列的优缺点
队列的优点
-
天然的顺序公平性
- FIFO原则:严格遵循先进先出规则,适用于需要保证公平性的场景(如:打印任务排队、客服呼叫系统)。
- 操作可预测:所有元素的处理顺序完全透明,易于调试和逻辑追踪。
-
高效的基础操作
- 时间复杂度稳定:入队(
enqueue
)和出队(dequeue
)操作均为 O(1)(数组循环队列/链表实现)。 - 低资源消耗:内存连续访问(数组)或指针操作(链表)均对硬件友好。
- 时间复杂度稳定:入队(
-
缓冲与解耦能力
- 流量削峰:应对突发请求时作为缓冲区,避免系统过载(如:消息队列Kafka)。
- 生产者-消费者解耦:平衡不同模块的处理速度差异(如:多线程任务分发)。
-
灵活的扩展性
- 多形态实现:可通过不同底层结构(数组、链表)或变形(循环队列、优先队列)适配需求。
- 支持泛型数据:可存储任意数据类型(如:C语言中的
void*
指针)。
队列的缺点
-
访问限制
- 无法随机访问:只能操作队头/队尾元素,查找中间元素需遍历队列(时间复杂度 O(n))。
- 修改成本高:若需调整元素顺序(如插队),必须重建队列或使用其他数据结构。
-
实现依赖的局限性
实现方式 缺点 静态数组 固定容量导致空间浪费或溢出风险 动态链表 内存碎片化、节点分配开销 循环队列 需预留空位判断队满(可用空间为 n-1
) -
并发场景挑战
- 线程安全问题:多线程同时操作需加锁(互斥锁/信号量),增加复杂度。
- 性能权衡:锁机制可能导致吞吐量下降(可通过无锁队列优化,但实现难度高)。
-
内存管理成本
- 动态扩展开销:链表实现的频繁内存分配/释放可能引发性能抖动。
- 缓存不友好:链表节点非连续存储,降低CPU缓存命中率(数组更优)。
实际应用中的取舍建议
-
优先队列的替代方案
- 当需要按优先级处理元素时,标准队列无法满足需求,需改用堆(Heap)或优先队列。
-
资源受限场景的优化
- 嵌入式系统中,静态数组+循环队列可避免动态内存分配的开销。
-
高并发场景的增强
- 使用无锁队列(如:环形缓冲区+原子操作)或任务分片提升吞吐量。
总结
队列如同数字世界中的秩序守护者,用最朴素的「先进先出」法则在混乱中建立规则。从算法面试中巧解BFS问题,到分布式系统中承载百万级消息的洪流,这种数据结构以简洁的哲学应对着复杂的挑战。它教会我们:高效的系统往往不是消灭等待,而是优雅地管理等待。
通过数组与链表的实现对比,我们看到了数据结构设计的权衡艺术——静态实现追求极致的性能可控,动态实现拥抱灵活的资源适配。无论是循环队列的精妙取模,还是链式节点的精准跳转,最终都服务于同一个目标:在正确的时间,以正确的方式,处理正确的请求。
当你再次面对需要顺序公平性的场景时,不妨让队列成为你的隐形协作者。而在未来的探索中,可以继续深入:
- 横向扩展:双端队列(Deque)如何突破FIFO限制?
- 纵向深入:优先队列(Priority Queue)怎样用堆重塑出队规则?
- 工程实践:Kafka/RabbitMQ等消息队列如何解决分布式一致性难题?
队列的魅力,恰在于它既是入门数据结构的基石,又是构建复杂系统的核心齿轮。正如交响乐团的指挥掌控节奏,学会驾驭队列的开发者,终将在秩序的韵律中写出优雅的技术乐章。
相关文章:

数据结构:队列篇
图均为手绘,代码基于vs2022实现 系列文章目录 数据结构初探: 顺序表 数据结构初探:链表之单链表篇 数据结构初探:链表之双向链表篇 链表特别篇:链表经典算法问题 数据结构:栈篇 文章目录 系列文章目录前言一.队列的概念和结构1.1概念一、动态内存管理优势二、操作效率与安全性…...

第05章 17 Contour 过滤器介绍与例子
vtkContourFilter 是 VTK(Visualization Toolkit)中的一个关键类,用于从输入数据生成等值线或等值面。它是基于阈值的过滤器,可以从标量字段中提取等值线或等值面。vtkContourFilter 的核心功能是根据用户指定的值生成等值线或等值…...

【落羽的落羽 数据结构篇】顺序表
文章目录 一、线性表二、顺序表1. 概念与分类2. 准备工作3. 静态顺序表4. 动态顺序表4.1 定义顺序表结构4.2 顺序表的初始化4.3 检查空间是否足够4.3 尾部插入数据4.4 头部插入数据4.5 尾部删除数据4.6 头部删除数据4.7 在指定位置插入数据4.8 在指定位置删除数据4.9 顺序表的销…...

AI编程:如何编写提示词
这是小卷对AI编程工具学习的第2篇文章,今天讲讲如何编写AI编程的提示词,并结合实际功能需求案例来进行开发 1.编写提示词的技巧 好的提示词应该是:目标清晰明确,具有针对性,能引导模型理解问题 下面是两条提示词的对…...

DeepSeek-R1 论文解读 —— 强化学习大语言模型新时代来临?
近年来,人工智能(AI)领域发展迅猛,大语言模型(LLMs)为通用人工智能(AGI)的发展开辟了道路。OpenAI 的 o1 模型表现非凡,它引入的创新性推理时缩放技术显著提升了推理能力…...

高阶C语言|深入理解字符串函数和内存函数
文章目录 前言1.求字符串长度1.1 字符串长度函数:strlen模拟实现 2.长度不受限制的字符串函数2.1 字符串拷贝函数:strcpy模拟实现 2.2 字符串连接函数:strcat模拟实现 2.3 字符串比较函数:strcmp模拟实现 3.长度受限制的字符串函数…...

UE学习日志#17 C++笔记#3 基础复习3
19.2 [[maybe_unused]] 禁止编译器在未使用某些内容时发出警告 19.3 [[noreturn]] 永远不会把控制权返回给调用点 19.4 [[deprecated]] 标记为已弃用,仍然可以使用但是不鼓励使用 可以加参数表示弃用的原因[[deprecated("")]] 19.5 [[likely]]和[[un…...

团体程序设计天梯赛-练习集——L1-028 判断素数
前言 一道10分的题目,相对来说比较简单,思考的时候要仔细且活跃,有时候在写代码的时候一些代码的出现很多余,并且会影响最后的结果 L1-028 判断素数 本题的目标很简单,就是判断一个给定的正整数是否素数。 输入格式…...

从0开始使用面对对象C语言搭建一个基于OLED的图形显示框架(基础图形库实现)
目录 基础图形库的抽象 抽象图形 抽象点 设计我们的抽象 实现我们的抽象 测试 抽象线 设计我们的抽象 实现我们的抽象 绘制垂直的和水平的线 使用Bresenham算法完成任意斜率的绘制 绘制三角形和矩形 矩形 三角形 实现 绘制圆,圆弧和椭圆 继续我们的…...

创新创业计划书|建筑垃圾资源化回收
目录 第1部分 公司概况........................................................................ 1 第2部分 产品/服务...................................................................... 3 第3部分 研究与开发.................................................…...

反射、枚举以及lambda表达式
一.反射 1.概念:Java的反射(reflection)机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性,既然能拿到那么&am…...

ROS应用之IMU碰撞检测与接触事件识别
前言 碰撞检测与接触事件识别是机器人与环境交互中的重要任务,尤其是在复杂的动态环境中。IMU(惯性测量单元)作为一种高频率、低延迟的传感器,因其能够检测加速度、角速度等动态变化而成为实现碰撞检测的核心手段之一。结合先进的…...

docker安装MySQL8:docker离线安装MySQL、docker在线安装MySQL、MySQL镜像下载、MySQL配置、MySQL命令
一、镜像下载 1、在线下载 在一台能连外网的linux上执行docker镜像拉取命令 docker pull mysql:8.0.41 2、离线包下载 两种方式: 方式一: -)在一台能连外网的linux上安装docker执行第一步的命令下载镜像 -)导出 # 导出镜…...

android安卓用Rime
之前 [1] 在 iOS 配置用上自改方案 [2],现想在安卓也用上。Rime 主页推荐了两个安卓平台支持 rime 的输入法 [3]: 同文 Tongwen Rime Input Method Editor,但在我的 Realme X2 Pro 上似乎有 bug:弹出的虚拟键盘只有几个 switcher…...

每日一博 - 三高系统架构设计:高性能、高并发、高可用性解析
文章目录 引言一、高性能篇1.1 高性能的核心意义 1.2 影响系统性能的因素1.3 高性能优化方法论1.3.1 读优化:缓存与数据库的结合1.3.2 写优化:异步化处理 1.4 高性能优化实践1.4.1 本地缓存 vs 分布式缓存1.4.2 数据库优化 二、高并发篇2.1 高并发的核心…...

C++ 中的引用(Reference)
在 C 中,引用(Reference)是一种特殊的变量类型,它提供了一个已存在变量的别名。引用在很多场景下都非常有用,比如函数参数传递、返回值等。下面将详细介绍 C 引用的相关知识。 1. 引用的基本概念和语法 引用是已存在…...

负荷预测算法模型
1. 时间序列分析方法 时间序列分析方法是最早被用来进行电力负荷预测的方法之一,它基于历史数据来构建数学模型,以描述时间与负荷值之间的关系。这种方法通常只考虑时间变量,不需要大量的输入数据,因此计算速度快。然而ÿ…...

【C语言】内存管理
【C语言】内存管理 文章目录 【C语言】内存管理1.概念2.库函数3.动态分配内存malloccalloc 4.重新调整内存的大小和释放内存reallocfree 1.概念 C 语言为内存的分配和管理提供了几个函数。这些函数可以在 <stdlib.h> 头文件中找到。 在 C 语言中,内存是通过…...

deepseek大模型本机部署
2024年1月20日晚,中国DeepSeek发布了最新推理模型DeepSeek-R1,引发广泛关注。这款模型不仅在性能上与OpenAI的GPT-4相媲美,更以开源和创新训练方法,为AI发展带来了新的可能性。 本文讲解如何在本地部署deepseek r1模型。deepseek官…...

动态规划DP 最长上升子序列模型 拦截导弹(题目分析+C++完整代码)
概览检索 动态规划DP 最长上升子序列模型 拦截导弹 原题链接 AcWiing 1010. 拦截导弹 题目描述 某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。 但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每…...

缩位求和——蓝桥杯
1.题目描述 在电子计算机普及以前,人们经常用一个粗略的方法来验算四则运算是否正确。 比如:248153720248153720 把乘数和被乘数分别逐位求和,如果是多位数再逐位求和,直到是 1 位数,得 24814>145 156 56 而…...

Baklib赋能企业实现高效数字化内容管理提升竞争力
内容概要 在数字经济的浪潮下,企业面临着前所未有的机遇与挑战。随着信息技术的迅猛发展,各行业都在加速推进数字化转型,以保持竞争力。在这个过程中,数字化内容管理成为不可或缺的一环。高效的内容管理不仅能够优化内部流程&…...

【视频+图文讲解】HTML基础2-html骨架与基本语法
图文教程 基本骨架 举个例子,下图所展示的为html的源代码 -!DOCTYPE:表示文档类型(后边写的html表示文档类型是html);其中“!”表示声明 只要是加这个声明标签的,浏览器就会把下边的源代码当…...

消息队列篇--原理篇--常见消息队列总结(RabbitMQ,Kafka,ActiveMQ,RocketMQ,Pulsar)
1、RabbitMQ 特点: AMQP协议:RabbitMQ是基于AMQP(高级消息队列协议)构建的,支持多种消息传递模式,如发布/订阅、路由、RPC等。多语言支持:支持多种编程语言的客户端库,包括Java、P…...

【力扣每日一题】存在重复元素 II 解题思路
219. 存在重复元素 II 解题思路 问题描述 给定一个整数数组 nums 和一个整数 k,要求判断数组中是否存在两个 不同的索引 i 和 j,使得: nums[i] nums[j]且满足 abs(i - j) < k 如果满足上述条件,返回 true,否则…...

React第二十八章(css modules)
css modules 什么是 css modules 因为 React 没有Vue的Scoped,但是React又是SPA(单页面应用),所以需要一种方式来解决css的样式冲突问题,也就是把每个组件的样式做成单独的作用域,实现样式隔离,而css modules就是一种…...

本地运行大模型效果及配置展示
电脑上用ollama安装了qwen2.5:32b,deepseek-r1:32b,deepseek-r1:14b,llama3.1:8b四个模型,都是Q4_K_M量化版。 运行过程中主要是cpu和内存负载比较大,qwen2.5:32b大概需要22g,deepseek-r1:32b类…...

愿景:做机器视觉行业的颠覆者
一个愿景,两场战斗,专注制胜。 一个愿景:做机器视觉行业的颠覆者。 我给自己创业,立一个大的愿景:做机器视觉行业的颠覆者。 两场战斗:无监督-大模型 上半场,无监督。2025-2030,共五…...

arm-linux-gnueabihf安装
Linaro Releases windows下打开wsl2中的ubuntu,资源管理器中输入: \\wsl$gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz 复制到/home/ark01/tool 在 Ubuntu 中创建目录: /usr/local/arm,命令如下: …...

力扣动态规划-16【算法学习day.110】
前言 ###我做这类文章一个重要的目的还是给正在学习的大家提供方向(例如想要掌握基础用法,该刷哪些题?建议灵神的题单和代码随想录)和记录自己的学习过程,我的解析也不会做的非常详细,只会提供思路和一些关…...