数据结构:队列篇

图均为手绘,代码基于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)
在这里插入图片描述

  • 队列既可以用数组实现,也可以用链表实现,但是在一般情况下,我们会选择使用链表进行实现
    但是为什么呢?

一、动态内存管理优势

  1. 按需扩容
    链表节点动态分配内存,队列长度无需预先声明上限。当数据规模不可预知时(如网络请求队列),链表可避免数组的「空间预分配浪费」或「频繁扩容」问题。

  2. 避免假溢出问题
    数组实现的循环队列需要预留一个空位判断队满,实际可用空间为MAX_SIZE-1,而链表天然支持动态增长,无此限制。但是在学习中我们使用数组实现循环队列会使得逻辑更加清晰明了,为了更好理解,我会在<<栈和队列特别篇:经典算法问题>>中为大家讲解其中的好处


二、操作效率与安全性

  1. 稳定的时间复杂度
    链表的入队(O(1))和出队(O(1))操作无需像动态数组那样触发内存拷贝,性能可预测性更强

  2. 内存碎片化更低
    频繁的数组扩容/缩容可能产生内存碎片,而链表的节点分散存储能更灵活利用内存空隙。

  3. 无数据搬迁开销
    数组出队时,若采用「非循环」结构需移动元素;链表只需修改指针指向,无数据搬移成本

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;
}

四.队列的优缺点

队列的优点

  1. 天然的顺序公平性

    • FIFO原则:严格遵循先进先出规则,适用于需要保证公平性的场景(如:打印任务排队、客服呼叫系统)。
    • 操作可预测:所有元素的处理顺序完全透明,易于调试和逻辑追踪。
  2. 高效的基础操作

    • 时间复杂度稳定:入队(enqueue)和出队(dequeue)操作均为 O(1)(数组循环队列/链表实现)。
    • 低资源消耗:内存连续访问(数组)或指针操作(链表)均对硬件友好。
  3. 缓冲与解耦能力

    • 流量削峰:应对突发请求时作为缓冲区,避免系统过载(如:消息队列Kafka)。
    • 生产者-消费者解耦:平衡不同模块的处理速度差异(如:多线程任务分发)。
  4. 灵活的扩展性

    • 多形态实现:可通过不同底层结构(数组、链表)或变形(循环队列、优先队列)适配需求。
    • 支持泛型数据:可存储任意数据类型(如:C语言中的void*指针)。

队列的缺点

  1. 访问限制

    • 无法随机访问:只能操作队头/队尾元素,查找中间元素需遍历队列(时间复杂度 O(n))。
    • 修改成本高:若需调整元素顺序(如插队),必须重建队列或使用其他数据结构。
  2. 实现依赖的局限性

    实现方式缺点
    静态数组固定容量导致空间浪费或溢出风险
    动态链表内存碎片化、节点分配开销
    循环队列需预留空位判断队满(可用空间为n-1
  3. 并发场景挑战

    • 线程安全问题:多线程同时操作需加锁(互斥锁/信号量),增加复杂度。
    • 性能权衡:锁机制可能导致吞吐量下降(可通过无锁队列优化,但实现难度高)。
  4. 内存管理成本

    • 动态扩展开销:链表实现的频繁内存分配/释放可能引发性能抖动。
    • 缓存不友好:链表节点非连续存储,降低CPU缓存命中率(数组更优)。

实际应用中的取舍建议

  1. 优先队列的替代方案

    • 当需要按优先级处理元素时,标准队列无法满足需求,需改用堆(Heap)优先队列
  2. 资源受限场景的优化

    • 嵌入式系统中,静态数组+循环队列可避免动态内存分配的开销。
  3. 高并发场景的增强

    • 使用无锁队列(如:环形缓冲区+原子操作)或任务分片提升吞吐量。

总结

队列如同数字世界中的秩序守护者,用最朴素的「先进先出」法则在混乱中建立规则。从算法面试中巧解BFS问题,到分布式系统中承载百万级消息的洪流,这种数据结构以简洁的哲学应对着复杂的挑战。它教会我们:高效的系统往往不是消灭等待,而是优雅地管理等待

通过数组与链表的实现对比,我们看到了数据结构设计的权衡艺术——静态实现追求极致的性能可控,动态实现拥抱灵活的资源适配。无论是循环队列的精妙取模,还是链式节点的精准跳转,最终都服务于同一个目标:在正确的时间,以正确的方式,处理正确的请求

当你再次面对需要顺序公平性的场景时,不妨让队列成为你的隐形协作者。而在未来的探索中,可以继续深入:

  • 横向扩展:双端队列(Deque)如何突破FIFO限制?
  • 纵向深入:优先队列(Priority Queue)怎样用堆重塑出队规则?
  • 工程实践:Kafka/RabbitMQ等消息队列如何解决分布式一致性难题?

队列的魅力,恰在于它既是入门数据结构的基石,又是构建复杂系统的核心齿轮。正如交响乐团的指挥掌控节奏,学会驾驭队列的开发者,终将在秩序的韵律中写出优雅的技术乐章。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/13181.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQL

二进制方式&#xff1a; 下载并上传安装包到设备 创建组与用户 [rootlocalhost ~]# groupadd mysql [rootlocalhost ~]# useradd -r -g mysql -s /bin/false mysql解压安装包&#xff1a; [rootlocalhost ~]# tar xf mysql-8.0.36-linux-glibc2.28-x86_64.tar.xz -C /usr/l…

Windows电脑本地部署运行DeepSeek R1大模型(基于Ollama和Chatbox)

文章目录 一、环境准备二、安装Ollama2.1 访问Ollama官方网站2.2 下载适用于Windows的安装包2.3 安装Ollama安装包2.4 指定Ollama安装目录2.5 指定Ollama的大模型的存储目录 三、选择DeepSeek R1模型四、下载并运行DeepSeek R1模型五、常见问题解答六、使用Chatbox进行交互6.1 …

洛谷网站: P3029 [USACO11NOV] Cow Lineup S 题解

题目传送门&#xff1a; P3029 [USACO11NOV] Cow Lineup S - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 前言&#xff1a; 这道题的核心问题是在一条直线上分布着不同品种的牛&#xff0c;要找出一个连续区间&#xff0c;使得这个区间内包含所有不同品种的牛&#xff0c;…

如何利用maven更优雅的打包

最近在客户现场部署项目&#xff0c;有两套环境&#xff0c;无法连接互联网&#xff0c;两套环境之间也是完全隔离&#xff0c;于是问题就来了&#xff0c;每次都要远程到公司电脑改完代码&#xff0c;打包&#xff0c;通过网盘&#xff08;如果没有会员&#xff0c;上传下载慢…

360手机刷机 360手机解Bootloader 360手机ROOT

360手机刷机 360手机解Bootloader 360手机ROOT 问&#xff1a;360手机已停产&#xff0c;现在和以后&#xff0c;能刷机吗&#xff1f; 答&#xff1a;360手机&#xff0c;是肯定能刷机的 360手机资源下载网站 360手机-360手机刷机RootTwrp 360os.top 360rom.github.io 一、…

8.攻防世界Web_php_wrong_nginx_config

进入题目页面如下 尝试弱口令密码登录 一直显示网站建设中&#xff0c;尝试无果&#xff0c;查看源码也没有什么特别漏洞存在 用Kali中的dirsearch扫描根目录试试 命令&#xff1a; dirsearch -u http://61.147.171.105:53736/ -e* 登录文件便是刚才登录的界面打开robots.txt…

排序算法--计数排序

唯一种没有比较的排序(指没有前后比较,还是有交换的)。统计每个元素出现的次数&#xff0c;直接计算元素在有序序列中的位置&#xff0c;要求数据是整数且范围有限。适用于数据为小范围整数&#xff08;如年龄、成绩&#xff09;&#xff0c;数据重复率较高时效率更优。可用于小…

PyTorch快速入门

Anaconda Anaconda 是一款面向科学计算的开源 Python 发行版本&#xff0c;它集成了众多科学计算所需的库、工具和环境管理系统&#xff0c;旨在简化包管理和部署&#xff0c;提升开发与研究效率。 核心组件&#xff1a; Conda&#xff1a;这是 Anaconda 自带的包和环境管理…

树莓派卷积神经网络实战车牌检测与识别

文章目录 树莓派介绍1. 树莓派的硬件规格2. 树莓派的操作系统3. 树莓派的应用场景 研究背景一、效果演示1.0 项目获取1.1 图像识别1.2 视频识别 二、技术原理2.1 整体流程2.2 CCPD数据集介绍2.3 车牌定位2.4 车牌矫正2.5 车牌识别2.5.1 CRNN概述2.5.2 CRNN网络架构实现2.5.3 CN…

Redis入门概述

1.1、Redis是什么 Redis&#xff1a;官网 高性能带有数据结构的Key-Value内存数据库 Remote Dictionary Server&#xff08;远程字典服务器&#xff09;是完全开源的&#xff0c;使用ANSIC语言编写遵守BSD协议&#xff0c;例如String、Hash、List、Set、SortedSet等等。数据…

如何在自己电脑上私有化部署deep seek

要在自己的电脑上私有化部署 DeepSeek&#xff0c;通常需要以下步骤&#xff1a; 1. 环境准备 操作系统&#xff1a;确保你的电脑操作系统支持 Docker 或直接安装 Python 环境&#xff08;如 Linux、Windows 或 macOS&#xff09;。 Python 环境&#xff1a;安装 Python 3.7 …

【办公类-99-01】20250201学具PDF打印会缩小一圈——解决办法:换一个PDF阅读器

背景需求&#xff1a; 2024年1月13日&#xff0c;快要放寒假了&#xff0c;组长拿着我们班的打印好的一叠教案来调整。 “前面周计划下面的家园共育有调整&#xff0c;你自己看批注。” “还有你这个教案部分的模版有问题&#xff0c;太小&#xff08;窄&#xff09;了。考虑…

k8s集群

文章目录 项目描述项目环境系统与软件版本概览项目步骤 环境准备IP地址规划关闭selinux和firewall配置静态ip地址修改主机名添加hosts解析 项目步骤一、使用kubeadm安装k8s单master的集群环境&#xff08;1个master2个node节点&#xff09;1、互相之间建立免密通道2.关闭交换分…

HTTP和HTTPS协议详解

HTTP和HTTPS协议详解 HTTP详解什么是http协议http协议的发展史http0.9http1.0http1.1http2.0 http协议的格式URI和URL请求request响应response http协议完整的请求与响应流程 HTTPS详解为什么使用HTTPSSSL协议HTTPS通信过程TLS协议 HTTP详解 什么是http协议 1、全称Hyper Tex…

2025开源DouyinLiveRecorder全平台直播间录制工具整合包,多直播同时录制、教学直播录制、教学视频推送、简单易用不占内存

一、DouyinLiveRecorder软件介绍&#xff08;文末提供下载&#xff09; 官方地址&#xff1a;GitHub - ihmily/DouyinLiveRecorder 本文信息来源于作者GitHub地址 一款简易的可循环值守的直播录制工具&#xff0c;基于FFmpeg实现多平台直播源录制&#xff0c;支持自定义配置录制…

学习threejs,pvr格式图片文件贴图

&#x1f468;‍⚕️ 主页&#xff1a; gis分享者 &#x1f468;‍⚕️ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕️ 收录于专栏&#xff1a;threejs gis工程师 文章目录 一、&#x1f340;前言1.1 ☘️PVR贴图1.2 ☘️THREE.Mesh…

Beans模块之工厂模块注解模块CustomAutowireConfigurer

博主介绍&#xff1a;✌全网粉丝5W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面有丰富的经验…

(一)DeepSeek大模型安装部署-Ollama安装

大模型deepseek安装部署 (一)、安装ollama curl -fsSL https://ollama.com/install.sh | sh sudo systemctl start ollama sudo systemctl enable ollama sudo systemctl status ollama(二)、安装ollama遇到网络问题&#xff0c;请手动下载 ollama-linux-amd64.tgz curl -L …

使用Pygame制作“贪吃蛇”游戏

贪吃蛇 是一款经典的休闲小游戏&#xff1a;玩家通过操控一条会不断变长的“蛇”在屏幕中移动&#xff0c;去吃随机出现的食物&#xff0c;同时要避免撞到墙壁或自己身体的其他部分。由于其逻辑相对简单&#xff0c;但可玩性和扩展性都不错&#xff0c;非常适合作为新手练习游戏…

【prompt实战】AI +OCR技术结合ChatGPT能力项目实践(BOL提单识别提取专家)

本文原创作者:姚瑞南 AI-agent 大模型运营专家,先后任职于美团、猎聘等中大厂AI训练专家和智能运营专家岗;多年人工智能行业智能产品运营及大模型落地经验,拥有AI外呼方向国家专利与PMP项目管理证书。(转载需经授权) 目录 1. 需求背景 2. 目标 3. BOL通用处理逻辑…