数据结构:队列篇

图均为手绘,代码基于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等消息队列如何解决分布式一致性难题?

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Python的那些事第十二篇:从入门到“不撞南墙不回头”Python 文件操作与异常处理

Python 文件操作与异常处理&#xff1a;从入门到“不撞南墙不回头” 目录 Python 文件操作与异常处理&#xff1a;从入门到“不撞南墙不回头” 一、引言 二、Python 文件操作 三、Python 异常处理 四、综合实例&#xff1a;学生成绩管理系统 五、总结与展望 一、引言 1.…

论文解读:《基于TinyML毫米波雷达的座舱检测、定位与分类》

摘要 本文提出了一种实时的座舱检测、定位和分类解决方案&#xff0c;采用毫米波&#xff08;mmWave&#xff09;雷达系统芯片&#xff08;SoC&#xff09;&#xff0c;CapterahCAL60S344-AE&#xff0c;支持微型机器学习&#xff08;TinyML&#xff09;。提出了波束距离-多普勒…

【B站保姆级视频教程:Jetson配置YOLOv11环境(七)Ultralytics YOLOv11配置】

Jetson配置YOLOv11环境&#xff08;7&#xff09;Ultralytics YOLOv11环境配置 文章目录 1. 下载YOLOv11 github项目2. 安装ultralytics包3. 验证ultralytics安装3.1 下载yolo11n.pt权重文件3.2 推理 1. 下载YOLOv11 github项目 创建一个目录&#xff0c;用于存放YOLOv11的项目…

代码讲解系列-CV(二)——卷积神经网络

文章目录 一、系列大纲二、卷积神经网络&#xff08;图像分类为例&#xff09;2.1 pytorch简介训练框架张量自动微分动态计算图更深入学习 2.2 数据输入和增强Dataset—— torch.utils.data.DatasetDataLoader——torch.utils.data.Dataloader数据增强 2.3 CNN设计与训练nn.Mod…

YK人工智能(六)——万字长文学会基于Torch模型网络可视化

1. 可视化网络结构 随着深度神经网络做的的发展&#xff0c;网络的结构越来越复杂&#xff0c;我们也很难确定每一层的输入结构&#xff0c;输出结构以及参数等信息&#xff0c;这样导致我们很难在短时间内完成debug。因此掌握一个可以用来可视化网络结构的工具是十分有必要的…

堆的基本概念

1.1 堆的基本概念 虚拟机所在目录 E:\ctf\pwn-self 进入虚拟机的pwndocker环境 holyeyesubuntu:~$ pwd /home/holyeyes holyeyesubuntu:~$ sudo ./1run.sh IDA分析 int __fastcall main(int argc, const char **argv, const char **envp) { void *v4; // [rsp20h] [rbp-1…

RabbitMQ深度探索:前置知识

消息中间件&#xff1a; 消息中间件基于队列模式实现异步 / 同步传输数据作用&#xff1a;可以实现支撑高并发、异步解耦、流量削峰、降低耦合 传统的 HTTP 请求存在的缺点&#xff1a; HTTP 请求基于响应的模型&#xff0c;在高并发的情况下&#xff0c;客户端发送大量的请求…

JDK9新特性

文章目录 新特性&#xff1a;1.模块化系统使用模块化module-info.java&#xff1a;exports&#xff1a;opens&#xff1a;requires&#xff1a;provides&#xff1a;uses&#xff1a; 2.JShell启动Jshell执行计算定义变量定义方法定义类帮助命令查看定义的变量&#xff1a;/var…

合宙air001使用命令行烧写固件

起因&#xff1a; 做了个小板子给同事使用&#xff0c;但同事没有air001的arduino编译烧录环境&#xff0c;安装的话&#xff0c;对于没有接触过arduino的人有些复杂&#xff0c;所以想着能不能直接烧录编译好的固件&#xff1b; 经过&#xff1a; 在网上搜索相关教程&#…

manimgl安装

一、环境 笔记本 $ lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 22.04.5 LTS Release: 22.04 Codename: jammy二、安装miniconda3 manimgl基于python开发&#xff0c;为了防止将笔记本中已有的python环境破坏&#xff0c;因此…

Python aiortc API

本研究的主要目的是基于Python aiortc api实现抓取本地设备&#xff08;摄像机、麦克风&#xff09;媒体流实现Web端预览。本文章仅仅描述实现思路&#xff0c;索要源码请私信我。 demo-server解耦 原始代码解析 http服务器端 import argparse import asyncio import json…

编程AI深度实战:大模型哪个好? Mistral vs Qwen vs Deepseek vs Llama

随着开源 LLM 的发展&#xff0c;越来越多的模型变得专业化&#xff0c;“代码”LLM 变得非常流行。这些 LLM 旨在比其 “常识” 对应物更小&#xff0c;但旨在超越更大的通用模型的编码性能。 这些模型以极低的成本提供大型模型的功能&#xff0c;进一步使本地 LLM 空间民主化…

DeepSeek:全栈开发者视角下的AI革命者

目录​​​​​​​ DeepSeek&#xff1a;全栈开发者视角下的AI革命者 写在前面 一、DeepSeek的诞生与定位 二、DeepSeek技术架构的颠覆性突破 1、解构算力霸权&#xff1a;从MoE架构到内存革命 2、多模态扩展的技术纵深 3、算法范式的升维重构 4、重构AI竞争规则 三、…

SQL入门到精通 理论+实战 -- 在 MySQL 中学习SQL语言

目录 一、环境准备 1、MySQL 8.0 和 Navicat 下载安装 2、准备好的表和数据文件&#xff1a; 二、SQL语言简述 1、数据库基础概念 2、什么是SQL 3、SQL的分类 4、SQL通用语法 三、DDL&#xff08;Data Definition Language&#xff09;&#xff1a;数据定义语言 1、操…

HTB:EscapeTwo[WriteUP]

目录 连接至HTB服务器并启动靶机 信息收集 使用rustscan对靶机TCP端口进行开放扫描 将靶机TCP开放端口号提取并保存 使用nmap对靶机TCP开放端口进行脚本、服务扫描 使用nmap对靶机TCP开放端口进行漏洞、系统扫描 使用nmap对靶机常用UDP端口进行开放扫描 使用nmap对靶机…

一文讲解Java中的ArrayList和LinkedList

ArrayList和LinkedList有什么区别&#xff1f; ArrayList 是基于数组实现的&#xff0c;LinkedList 是基于链表实现的。 二者用途有什么不同&#xff1f; 多数情况下&#xff0c;ArrayList更利于查找&#xff0c;LinkedList更利于增删 由于 ArrayList 是基于数组实现的&#…

多无人机--强化学习

这个是我对于我的大创项目的构思&#xff0c;随着时间逐渐更新 项目概要 我们的项目平台来自挑战杯揭绑挂帅的无人机对抗项目&#xff0c;但是在由于时间原因&#xff0c;并未考虑强化学习&#xff0c;所以现在通过大创项目来弥补遗憾 我们项目分为三部分&#xff0c;分为虚…

龙芯+FreeRTOS+LVGL实战笔记(新)——16数码管驱动

本专栏是笔者另一个专栏《龙芯+RT-Thread+LVGL实战笔记》的姊妹篇,主要的区别在于实时操作系统的不同,章节的安排和任务的推进保持一致,并对源码做了完善与优化,各位可以前往本人在B站的视频合集(图1所示)观看所有演示视频,合集首个视频链接为: https://www.bilibili.…

Day36-【13003】短文,数组的行主序方式,矩阵的压缩存储,对称、三角、稀疏矩阵和三元组线性表,广义表求长度、深度、表头、表尾等

文章目录 本次课程内容第四章 数组、广义表和串第一节 数组及广义表数组的基本操作数组的顺序存储方式-借用矩阵行列式概念二维数组C语言对应的函数-通常行主序方式 矩阵的压缩存储对称矩阵和三角矩阵压缩存储后&#xff0c;采用不同的映射函数稀疏矩阵-可以构成三元组线性表三…

[Python人工智能] 四十九.PyTorch入门 (4)利用基础模块构建神经网络并实现分类预测

从本专栏开始,作者正式研究Python深度学习、神经网络及人工智能相关知识。前文讲解PyTorch构建回归神经网络。这篇文章将介绍如何利用PyTorch构建神经网络实现分类预测,其是使用基础模块构建。前面我们的Python人工智能主要以TensorFlow和Keras为主,而现在最主流的深度学习框…