【数据结构】环形队列(循环队列)学习笔记总结

news2024/11/18 17:27:46

文章目录

  • 什么是环形队列?
  • 基于 C 语言实现环形队列
  • 环形队列的应用场合

在计算机科学中,数据结构是组织和存储数据的方式,它对于高效的算法设计至关重要。队列是一种常见的数据结构,遵循 FIFO(先进先出,First-In-First-Out)的原则。然而,在实际应用中,传统的线性队列可能会遇到一些问题,例如当队列满时无法再插入新元素,即使队列头部有空闲空间。为了解决这些问题, 环形队列(也称为 循环队列)应运而生。

环形队列通过将数组的末端与开头连接起来形成一个循环结构,从而更有效地利用内存空间。这种数据结构在许多应用场景中都非常有用,包括缓冲区管理、生产者-消费者问题、日志记录、消息队列等。本文是我学习环形队列时所作笔记的总结,并使用 C 语言实现一个高效且功能丰富的环形队列,以及探讨其在实际项目中的应用。

在这里插入图片描述

什么是环形队列?

为充分利用向量空间,克服 “ 假溢出1” 现象的方法是:将向量空间想象为一个首尾相接的圆环,并称这种向量为循环向量。存储在其中的队列称为循环队列(Circular Queue)。循环队列是把顺序队列首尾相连,把存储队列元素的表从逻辑上看成一个环,成为循环队列。

——引用至《循环队列_百度百科》

要理解环形队列,可以先从单个变量的读写开始。

以串口收发数据为例,假设某些外设通过串口向主机发送 1 byte 的数据,且每秒发送一次,那么这 1 byte 的数据会先存在主机的接收缓冲存储区中。假设主机的串口接收缓冲存储区大小也刚好为 1 byte,一秒的间隔时间足够主机将缓冲存储区的数据及时读走并处理。

在这里插入图片描述

但是,如果发送的数据不止 1 byte,而是多个字节的情况,那么可能就会存在某个数据还没被读取,接收缓冲存储区就被下一个数据覆盖的情况。这时可以用中断的方式来处理这种情况,只要一数据存入接收缓冲存储区,就立刻触发中断把数据取走,存入其他存储区。不过,这样也有一个问题,如果上一个数据还没处理完毕,也会被中断程序覆盖为下一个数据,同样存在数据丢失的情况。

解决这个问题的办法也很简单,用一个相对较大的存储区,就可以存放更多的还没来得及处理的数据。根据先收到的数据先处理的原则,就可以用顺序队列作为这个存储区。

顺序队列有队头和队尾,而且队头和队尾的位置是可以变化的,所以需要设置两个指针 frontrear 分别指示队头元素和队尾元素在向量空间中的位置,它们的初值在队列初始化时均应设置为 0。

新元素出入队列的动作称为入队,入队时将新元素插入 rear 所指的位置,然后将 rear 加 1,即指向下一个没有数据的空间。元素从队列被读出的动作成为出队,出队时,读取 front 所指的元素后将元素删去,然后将 front 加 1 并,即指向下一个待处理数据的空间。

在这里插入图片描述

使用顺序队列后,即使来不急处理的数据,也能被逐个存储起来等待处理。但是,仍然存在一些弊端,根据顺序队列的特点,如果队列有元素,队头指针始终指向队头元素,队尾指针始终指向队尾元素的下一位置。所以当头尾指针相等时,队列为空。那么就会存在一种这样的情况(如下图所示),队列的空间并不是无限大的,当 rear 等于队列总长时,说明队列已经 “满” 了。此时若再执行入队操作,便会出现队满 “溢出”。然而,由于在此之前可能也执行了若干次出队操作.因而队列的前面部分可能还有很多闲置的元素空间,即这种溢出并非是真的没有可用的存储空间,故称这种溢出现象为 “假溢出”。

在这里插入图片描述

如果还有数据要入队但又无法入队,势必会造成数据丢失的情况,因此要一定解决队列这种 “假溢出” 的问题,否则使用队列就没有太多价值。

队列的 “假溢出” 现象,是因为又很多空间没有利用到,那么克服这种现象的办法就是:将队列空间想象为一个首尾相接的圆环,并称这种队列为环形队列,也叫循环队列。

循环队列就是将队列存储空间的最后一个位置绕到第一个位置,形成逻辑上的环状空间,供队列循环使用。在循环队列结构中,当存储空间的最后一个位置已被使用而再要进入队运算时,只需要存储空间的第一个位置空闲,便可将元素加入到第一个位置,即将存储空间的第一个位置作为队尾,可以更简单防止 “假溢出” 的发生。

在这里插入图片描述

更加形象的表示方法如下图所示,环形队列同样需要两个指针分别表示队头(head 指针)和队尾( tail 指针),与顺序队列的队头和队尾大致相同,入队时由队尾 tail 指针写入,并向后偏移;出队时由队头 head 指针取出后删除,并向后偏移。

在这里插入图片描述

环形队列有一种特殊情况与顺序队列不同,顺序队列的 frontrear 相等时,队列为空;而环形队列的 headtail 相等时,既可能是队列为空,也可能是队列为满。为了区别这两种结果,通常规定环形队列最多只能有 MAX_SIZE - 1 个队列元素(MAX_SIZE 为环形队列的空间大小),当环形队列中只剩下一个空存储单元时,就认为队列就已经满了。因此,环形队列判为空的条件是 head = tail,而环形队列判满的条件是 head=(tail + 1) % MAX_SIZE

基于 C 语言实现环形队列

为了更好的利用环形队列,创建一个环形队列的库可以在任何 C 语言项目中方便地使用队列数据结构。环形队列是一种基于数组的队列实现,在使用数组构建的方式,在访问内存空间时会更加方便。

实现一个简单的环形队列库,需要现实包括基本的操作如初始化、入队、出队、检查队列是否为空和是否已满等等功能。基本变量就是队列本身、队头、队尾、队列容量(队列长度)等,为方便管理这些变量,可以用一个结构体包含起来:

typedef struct {
    uint8_t *data;         // 存储数据的数组
    uint32_t head;         // 队头索引
    uint32_t tail;         // 队尾索引
    uint32_t capacity;     // 队列容量
    uint32_t count;        // 当前队列中的元素数量
} circular_queue_t;

[!NOTE]

我在这里多添加了一个 count,用于表示当前队列中的元素数量,这个变量不是必要的,只是可以更加简单的管理队列。

创建好队列后,第一件要做的事情就是初始化队列,所以环形队列库第一个功能就是初始化队列:

void circular_queue_init(circular_queue_t *queue, uint8_t *data, uint32_t capacity)
{
    queue->data = data;
    queue->head = 0;
    queue->tail = 0;
    queue->capacity = capacity;
    queue->count = 0;
}

队列初始化完毕,就可以进行入队出队的操作了。首先是入队操作,如果没有元素在队列中,就谈不上出队了。入队成功返回 true,否则返回 false

bool circular_queue_enqueue(circular_queue_t *queue, uint8_t value)
{
    if (circular_queue_is_full(queue))
        return false;  // 队列已满,无法入队

    queue->data[queue->tail] = value;
    queue->tail = (queue->tail + 1) % queue->capacity;
    queue->count++;
    return true;
}

出队操作与入队操作大致相同:

bool circular_queue_dequeue(circular_queue_t *queue, uint8_t *value)
{
    if (circular_queue_is_empty(queue))
        return false;  // 队列为空,无法出队

    *value = queue->data[queue->head];
    queue->head = (queue->head + 1) % queue->capacity;
    queue->count--;
    return true;
}

正常来说,有以上三个函数就可以使用环形队列完成各种项目了。当然,也可以在这些功能的基础上添加其他的功能,这里不对其他功能一一阐述,下面是完整的 .h.c 文件:

环形队列库头文件(circular_queue.h)

#ifndef __CIRCULAR_QUEUE_H__
#define __CIRCULAR_QUEUE_H__

#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>

// 定义队列结构体
typedef struct {
    uint8_t *data;         // 存储数据的数组
    uint32_t head;         // 队头索引
    uint32_t tail;         // 队尾索引
    uint32_t capacity;     // 队列容量
    uint32_t count;        // 当前队列中的元素数量
} circular_queue_t;

// 初始化队列
void circular_queue_init(circular_queue_t *queue, uint8_t *data, uint32_t capacity);
// 入队操作
bool circular_queue_enqueue(circular_queue_t *queue, uint8_t value);
// 出队操作
bool circular_queue_dequeue(circular_queue_t *queue, uint8_t *value);
// 检查队列是否为空
bool circular_queue_is_empty(const circular_queue_t *queue);
// 检查队列是否已满
bool circular_queue_is_full(const circular_queue_t *queue);
// 获取队列大小
uint32_t circular_queue_size(const circular_queue_t *queue);
// 查看队头元素
bool circular_queue_peek_head(const circular_queue_t *queue, uint8_t *value);
// 查看队尾元素
bool circular_queue_peek_tail(const circular_queue_t *queue, uint8_t *value);
// 清空队列
void circular_queue_clear(circular_queue_t *queue);

#endif // __CIRCULAR_QUEUE_H__

环形队列库源文件(circular_queue.c)

#include "circular_queue.h"

// 初始化队列
void circular_queue_init(circular_queue_t *queue, uint8_t *data, uint32_t capacity)
{
    queue->data = data;
    queue->head = 0;
    queue->tail = 0;
    queue->capacity = capacity;
    queue->count = 0;
}

// 入队操作
bool circular_queue_enqueue(circular_queue_t *queue, uint8_t value)
{
    if (circular_queue_is_full(queue))
        return false;  // 队列已满,无法入队

    queue->data[queue->tail] = value;
    queue->tail = (queue->tail + 1) % queue->capacity;
    queue->count++;
    return true;
}

// 出队操作
bool circular_queue_dequeue(circular_queue_t *queue, uint8_t *value)
{
    if (circular_queue_is_empty(queue))
        return false;  // 队列为空,无法出队

    *value = queue->data[queue->head];
    queue->head = (queue->head + 1) % queue->capacity;
    queue->count--;
    return true;
}

// 检查队列是否为空
bool circular_queue_is_empty(const circular_queue_t *queue)
{
    return queue->count == 0;
}

// 检查队列是否已满
bool circular_queue_is_full(const circular_queue_t *queue)
{
    return queue->count == queue->capacity;
}

// 获取队列大小
uint32_t circular_queue_size(const circular_queue_t *queue)
{
    return queue->count;
}

// 查看队头元素
bool circular_queue_peek_head(const circular_queue_t *queue, uint8_t *value)
{
    if (circular_queue_is_empty(queue))
        return false;  // 队列为空,无法查看队头元素

    *value = queue->data[queue->head];
    return true;
}

// 查看队尾元素
bool circular_queue_peek_tail(const circular_queue_t *queue, uint8_t *value)
{
    if (circular_queue_is_empty(queue))
        return false;  // 队列为空,无法查看队尾元素

    *value = queue->data[(queue->tail - 1 + queue->capacity) % queue->capacity];
    return true;
}

// 清空队列
void circular_queue_clear(circular_queue_t *queue)
{
    queue->head = 0;
    queue->tail = 0;
    queue->count = 0;
    memcpy(queue->data, 0, queue->capacity);
    queue->capacity = 0;
}

环形队列库的功能说明如下:

  • circular_queue_init:初始化队列,设置队列的数据指针、容量和其他属性。
  • circular_queue_enqueue:向队列中添加一个元素。如果队列已满,则返回 false
  • circular_queue_dequeue:从队列中移除并返回一个元素。如果队列为空,则返回 false
  • circular_queue_is_empty:检查队列是否为空。
  • circular_queue_is_full:检查队列是否已满。
  • circular_queue_size:返回当前队列中的元素数量。
  • circular_queue_peek_head:返回队头的元素,但不移除它。如果队列为空,则返回 false
  • circular_queue_peek_tail:返回队尾的元素,但不移除它。如果队列为空,则返回 false
  • circular_queue_clear:将队列清空,但不释放内存。

该库提供了一个基本的循环队列实现,当然也可以根据自己的需要进行扩展和修改。例如,当前的队列只支持 uint_8 数据类型的数组,可以改为 void 型数组,再配合枚举型变量,让队列类型可以兼容更多类型。

环形队列的应用场合

  1. 缓冲区管理

    • 输入/输出缓冲:在操作系统或嵌入式系统中,环形队列常用于处理输入/输出缓冲。例如,键盘输入、串口通信、网络数据包等。
    • 音频/视频流:在多媒体应用中,环形队列用于存储音频或视频数据块,确保平滑的数据流。
  2. 生产者-消费者问题

    • 多线程同步:在多线程或多进程环境中,环形队列是解决生产者-消费者问题的经典方法。生产者将数据放入队列,消费者从队列中取出数据,这样可以避免竞争条件和死锁。
    • 任务调度:在任务调度系统中,环形队列用于存储待处理的任务,确保任务按顺序执行。
  3. 日志记录

    • 事件日志:在系统监控和日志记录中,环形队列可以用来存储最近的日志条目。当队列满时,新的日志条目会覆盖最早的条目,从而保持最新的日志记录。
  4. 缓存机制

    • LRU缓存:在实现最近最少使用(Least Recently Used, LRU)缓存时,环形队列可以用来存储最近使用的项目。当缓存满时,最旧的项目会被移除。
  5. 消息队列

    • 异步通信:在分布式系统或消息传递系统中,环形队列可以作为消息队列,用于异步通信。生产者发送消息到队列,消费者从队列中接收消息。
  6. 实时系统

    • 实时数据处理:在实时系统中,环形队列用于存储传感器数据或其他实时数据,确保数据的及时处理。
  7. 游戏开发

    • 事件处理:在游戏中,环形队列可以用来存储玩家输入事件或其他游戏事件,确保事件按顺序处理。
  8. 硬件驱动程序

    • DMA传输:在直接内存访问(Direct Memory Access, DMA)传输中,环形队列用于存储传输的数据块,确保数据的连续性和高效性。
  9. 性能监控

    • 采样数据:在性能监控工具中,环形队列可以用来存储性能指标的采样数据,以便进行分析和报告。
  10. 算法实现

    • 滑动窗口算法:在一些算法中,如滑动窗口平均值计算或滑动窗口最大值/最小值计算,环形队列可以用来存储窗口内的数据。

  1. 队列存储区还没有满,但队列却发生了溢出,通常把这种现象称为 “假溢出”。 ↩︎

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【AIGC】ChatGPT提示词助力自媒体内容创作升级

博客主页&#xff1a; [小ᶻZ࿆] 本文专栏: AIGC | ChatGPT 文章目录 &#x1f4af;前言&#x1f4af;高效仿写专家级文章提示词使用方法 &#x1f4af;CSDN博主账号分析提示词使用方法 &#x1f4af;自媒体爆款文案优化助手提示词使用方法 &#x1f4af;小结 &#x1f4af…

外贸网站怎么搭建对谷歌seo比较好?

外贸网站怎么搭建对谷歌seo比较好&#xff1f;搭建一个网站自然不复杂&#xff0c;但要想搭建一个符合谷歌seo规范的网站&#xff0c;那就要多注意了&#xff0c;你的网站做的再酷炫&#xff0c;再花里胡哨&#xff0c;但如果页面都是js代码&#xff0c;或者页面没有源代码内容…

解决Windows远程桌面 “为安全考虑,已锁定该用户账户,原因是登录尝试或密码更改尝试过多,请稍后片刻再重试,或与系统管理员或技术支持联系“问题

根本原因就是当前主机被通过远程桌面输入了过多的错误密码&#xff0c;被系统锁定。这种情况多数是你的服务器远程桌面被人试图攻击了&#xff0c;不建议取消系统锁定策略。如果阿里云或者腾讯云主机&#xff0c;只需要在管理后台通过管理终端或者VNC登陆一次&#xff0c;锁定即…

友元运算符重载函数

目录 1.定义友元运算符重载函数的语法形式 2.双目运算符重载 3.单目运算符重载 1.定义友元运算符重载函数的语法形式 &#xff08;1&#xff09;在类的内部&#xff0c;定义友元运算符重载函数的格式如下&#xff1a; friend 函数类型 operator 运算符&#xff08;形参表&a…

韩媒专访CertiK首席商务官:持续关注韩国市场,致力于解决Web3安全及合规问题

作为Web3.0头部安全公司&#xff0c;CertiK在KBW期间联合CertiK Ventures举办的活动引起了业界的广泛关注。CertiK一直以来与韩国地方政府保持着紧密合作关系&#xff0c;在合规领域提供强有力的支持。而近期重磅升级的CertiK Ventures可以更好地支持韩国本地的区块链项目。上述…

算法复杂度-空间

一 . 空间复杂度 空间复杂度也是一个数学表达式 &#xff0c; 是对一个算法在运行过程中因为算法的需要额外临时开辟的空间。 空间复杂度不是程序占用了多少个 bytes 的空间 &#xff0c; 因为常规情况每个对象大小差异不会很大 &#xff0c; 所以空间复杂度算的是变量的个数…

计算机毕业设计 智能旅游推荐平台的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

Kafka:架构与核心机制

Apache Kafka 是一种高吞吐量的分布式消息队列&#xff0c;广泛应用于实时数据流处理和大数据架构中。本文将详细探讨 Kafka 的架构、Replica 管理、消息读取、分区策略、可靠性保障等核心机制。 1. Kafka 的架构 1.1 组件概述 Kafka 的架构由多个组件构成&#xff0c;主要包…

Hashcat

简介 简单介绍下强大的hashcat爆破工具&#xff0c;本文分析了针对不同类型的文件以及系统密码的破解手段 office文档 查找hashcat模式命令hashcat -h|grep -i Office 可以查找所有的office破解类型 使用office2john.py获取加密office的哈希 最后使用hashcat掩码爆破&…

Android中的Activity与Fragment:深入解析与应用场景

在Android应用开发中&#xff0c;Activity和Fragment是两个核心概念&#xff0c;它们各自扮演着不同的角色&#xff0c;共同构成了用户界面的基础。理解并熟练掌握这两个组件的使用&#xff0c;对于开发高效、灵活且用户友好的Android应用至关重要。本文将深入解析Activity与Fr…

小柴冲刺软考中级嵌入式系统设计师系列二、嵌入式系统硬件基础知识(3)嵌入式系统的存储体系

目录 感悟 一、存储系统的层次结构 存储器系统 二、内存管理单元 三、RAM和ROM的种类与选型 1、RAM RAM分类 2、ROM ROM分类 四、高速缓存Cache 五、其他存储设备 flechazohttps://www.zhihu.com/people/jiu_sheng 小柴冲刺软考中级嵌入式系统设计师系列总目录https…

有关若依菜单管理的改造

导言&#xff1a; 搞了个后端对接若依前端&#xff0c;对接菜单管理时候懵懵的就搞完了&#xff0c;也是搞了很久。记一下逻辑和要注意的东西&#xff0c;以后做想似的能有个改造思路。 逻辑&#xff1a; 主要是要把后端传过的数组列表做成类似 这样的&#xff0c;所以要转格式…

【RocketMQ】RocketMQ安装

&#x1f3af; 导读&#xff1a;该文档记录了在Linux环境下使用Apache RocketMQ的安装与配置流程&#xff0c;包括下载RocketMQ压缩包、上传至服务器并解压、配置环境变量及对nameServer和broker的运行脚本和配置文件进行调整。文档还提供了使用Docker安装部署的方法&#xff0…

代码随想录Day17 图论-3

并查集理论基础 学习并查集 我们就要知道并查集可以解决什么问题 并查集主要有两个功能&#xff1a; 将两个元素添加到一个集合中判断两个元素在不在同一个集合 以下是代码模板 int n 1005; // n根据题目中节点数量而定&#xff0c;一般比节点数量大一点就好 vector<i…

jetlinks物联网平台学习4:http协议设备接入

http协议设备接入 1、创建产品2、配置设备接入方式3、配置网络组件4、上传消息协议5、填写网关信息6、配置http认证信息7、配置物模型8、创建设备9、使用apiPost模拟设备接入9.1、设备上线9.2、设备上报属性9.3、设备事件上报 1、创建产品 创建产品 2、配置设备接入方式 点击…

【Echarts】折线图和柱状图如何从后端动态获取数据?

&#x1f680;个人主页&#xff1a;一颗小谷粒 &#x1f680;所属专栏&#xff1a;Web前端开发 很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~ 目录 1.1 前端数据分析 1.2 数据库表分析 1.3 后端数据处理 1.4 前端接收数据 继上一篇文章&…

开源图像降噪算法与项目介绍【持续更新】

Intel Open Image Denoise 介绍&#xff1a;Intel Open Image Denoise&#xff08;OIDN&#xff09;是一个开源库&#xff0c;它提供了一系列高性能、高质量的去噪滤镜&#xff0c;专门用于光线追踪渲染的图像。这个库是Intel Rendering Toolkit的一部分&#xff0c;并且是在宽…

基因功能分析:DAP-seq与H3K4me3 ChIP-seq的协同效应

什么是DAP-Seq&#xff1f; DAP-Seq&#xff0c;即DNA亲和纯化测序技术&#xff0c;是一种创新的基因组分析方法。它通过体外表达转录因子&#xff0c;精确地鉴定转录因子与基因组的结合位点。与传统的ChIP-seq相比&#xff0c;DAP-Seq不受抗体和物种的限制&#xff0c;使得研…

springboot+vue+java校园共享厨房菜谱系统

目录 功能介绍使用说明系统实现截图开发核心技术介绍&#xff1a;开发步骤编译运行核心代码部分展示开发环境需求分析详细视频演示源码获取 功能介绍 用户 首页&#xff1a;展示系统基本信息&#xff0c;包括厨房介绍、使用指南、最新公告等。 登录注册&#xff1a;用户注册账…

【Qt笔记】QFrame控件详解

目录 引言 一、QFrame的基本特性 二、QFrame的常用方法 2.1 边框形状&#xff08;Frame Shape&#xff09; 2.2 阴影样式&#xff08;Frame Shadow&#xff09; 2.3 线条宽度&#xff08;Line Width&#xff09; 2.4 样式表(styleSheet) 三、QFrame的应用场景 四、应用…