循环队列(Ring Buffer)

news2024/10/6 14:25:39

背景: 最近在复习数据结构和算法,顺带刷刷题,虽然很长时间不刷题了但还是原来熟悉的味道,每一次重学都是加深了上一次的理解。本次我们看一下 循环队列(Ring Buffer),C语言实现。

循环队列:首先 它是一个队列,实现的功能就是 先进先出,后进后出,FIFO (First In First Out) ,而所谓的" 循环队列 " 其 实就是 将 队列 首尾相接来实现 队列的功能,为什么要首尾相接呢? 或者说 首尾相接 有什么好处,或 解决了什么问题?

1. 队列 

首先说 队列,这个数据结构底层是由数组或链表组成, 由数组构成的队列称为 “顺序队列”,由链表实现的队列称为“链式队列”。类似于初高中时候, 在食堂的窗口进行排队打饭,挨着窗口的同学是 队首, 最后一个同学是队尾,按照先来后到的顺序,你需要排在最后一个同学的后面。

一般情况下,我们都会使用“顺序队列”来实现,因为数组比链表在某些方面更加高效。

顺序队列如下图所示:

 顺序队列是一个数组, 对它的操作有 入队和出队,然后判断这个队列是否 满或空,以及得到当前队首和队尾的数据,共6个方法。

当我们在顺序队列中依次进行时,会出现 队首快到了最大索引,和队尾到了 数组的最大索引位置,此时出现入队操作,无法进行入队,前面的空闲位置又无法利用。

有一个方法是 将 front到rear指向的数据,进行移位,然后再进行入队操作。  这种方式需要移动数据,性能较差且耗时较长, 也就是说需要 “数据搬移”,这个操作时间复杂度为 O(n).

 于是 出现了循环队列,它解决了数据搬移的问题,使整个操作的时间降低,这就是“循环队列”

那我们如何实现一个循环队列呢? 这也是LeetCode 第622题,如下图所示:

题解一: 使用 count 来记录 当前队列的元素个数来判断是否满或空

front指向队首,rear指向队尾应插入的位置, 这个队列的大小为k,申请空间也是k,所以它的占用率达到了100%,  我们无法让rear指向队尾,只能指向应当插入的地方,为什么呢 ?  

假设这个 当初始状态为 front = 1  rear = 0 时候,这种对于 队列size = 1时无法通过案例,因为这个方式最小的队列是1,所以无法使 front=1,rear=0.

初始状态: front = 0     rear = 0      //  "空的状态"

加入一个: front = 0     rear = 1      //  此后加入多个

临界状态: front = 0     rear = k-1   //  此时 索引 k-1 位置,这时候还有一个位置可用

再加一个: front = 0     rear = 0      //  此时是真的满了

“初始状态”和 “再加一个” 的状态 是相同的索引,我们无法区分是满还是空,所以我们引入 count值来认定当前队列是空还是满。

认为 初始状态,也就是空的状态,即 count == 0 ,每次入队就count++,每次出队就 count -- ,这种整体的利用率为 k/(k),利用率为100%,我们用C语言实现一下,以下代码通过了LeetCode,请放心食用。

typedef struct {
    int front;
    int rear;
    int size;
    int *arr;
    int count;    
} MyCircularQueue;
bool myCircularQueueIsEmpty(MyCircularQueue* obj);
bool myCircularQueueIsFull(MyCircularQueue* obj);
MyCircularQueue* myCircularQueueCreate(int k) {
    MyCircularQueue* p = malloc(sizeof(MyCircularQueue));
    p->size = k ;
    p->arr = malloc(sizeof(int)*(k));
    p->front = 0;
    p->rear = 0;
    p->count = 0;
    return p;
}

bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
  if (myCircularQueueIsFull(obj)){
      return false;
  }else {
      obj->count++;
      *(obj->arr + obj->rear) = value;
      obj->rear = (obj->rear + 1)%(obj->size);
      return true;
  }
}

bool myCircularQueueDeQueue(MyCircularQueue* obj) {
  if (myCircularQueueIsEmpty(obj)){
      return false;
  }else {
      obj->front = (obj->front + 1)%(obj->size);
      obj->count--;
      return true;
  }
}

int myCircularQueueFront(MyCircularQueue* obj) {
  if (myCircularQueueIsEmpty(obj)){
      return -1;
  }else {
      return *(obj->arr + obj->front);
  }
}

int myCircularQueueRear(MyCircularQueue* obj) {
  if (myCircularQueueIsEmpty(obj)){
      return -1;
  }else {
      int temp = (obj->rear - 1 + obj->size) % (obj->size);
      return *(obj->arr + temp);
  }
}

bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
    if ( obj->count == 0 ){
        return true;
    }else{
        return false;
    }
  
}

bool myCircularQueueIsFull(MyCircularQueue* obj) {
    if ( obj->count == obj->size ){
        return true;
    }else{
        return false;
    }
}

void myCircularQueueFree(MyCircularQueue* obj) {
    free(obj->arr);
    free(obj);
}

题解二:front指向队首,rear指向队尾应插入的位置

初始状态: front = 0     rear = 0      //  "空的状态"

加入一个: front = 0     rear = 1      //  此后加入多个

临界状态: front = 0     rear =  k     //  此时 索引 k 位置,这时候还有一个位置可用

再加一个: front = 0     rear = 0      //  此时是真的满了,这种情况与 初始状态一样,所以无法区分满和空。

所以我们退而求其次,使用 “临界状态” 认为 “满的状态”, 即  (rear+1)%(k+1) == front

认为 初始状态,也就是空的状态,即 rear == front ,这种整体的利用率为 k/(k+1),当k趋于无穷大时候,利用率为100%,我们用C语言实现一下,以下代码通过了LeetCode,请放心食用。

typedef struct {
    int front;
    int rear;
    int size;
    int *arr;    
} MyCircularQueue;
bool myCircularQueueIsEmpty(MyCircularQueue* obj);
bool myCircularQueueIsFull(MyCircularQueue* obj);
MyCircularQueue* myCircularQueueCreate(int k) {
    MyCircularQueue* p = malloc(sizeof(MyCircularQueue));
    p->size = k + 1;
    p->arr = malloc(sizeof(int)*(k+1));
    p->front = 0;
    p->rear = 0;
    return p;
}

bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
  if (myCircularQueueIsFull(obj)){
      return false;
  }else {
      *(obj->arr + obj->rear) = value;
      obj->rear = (obj->rear + 1)%(obj->size);
      return true;
  }
}

bool myCircularQueueDeQueue(MyCircularQueue* obj) {
  if (myCircularQueueIsEmpty(obj)){
      return false;
  }else {
      obj->front = (obj->front + 1)%(obj->size);
      return true;
  }
}

int myCircularQueueFront(MyCircularQueue* obj) {
  if (myCircularQueueIsEmpty(obj)){
      return -1;
  }else {
      return *(obj->arr + obj->front);
  }
}

int myCircularQueueRear(MyCircularQueue* obj) {
  if (myCircularQueueIsEmpty(obj)){
      return -1;
  }else {
      int temp = obj->rear -1 ;
      if (temp < 0)temp = temp + obj->size;
      return *(obj->arr + temp);
  }
}

bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
    if ( obj->rear == obj->front ){
        return true;
    }else{
        return false;
    }
  
}

bool myCircularQueueIsFull(MyCircularQueue* obj) {
    if ( (obj->rear + 1)%(obj->size) == obj->front ){
        return true;
    }else{
        return false;
    }
}

void myCircularQueueFree(MyCircularQueue* obj) {
    free(obj->arr);
    free(obj);
}

题解三: front指向队首,rear指向队尾

初始状态: front = 1     rear = 0      //  "空的状态"

加入一个: front = 1     rear = 1      //  此后加入多个

临界状态: front = 1     rear = k      //  索引 0位置,这时候还有一个位置可用

再加一个: front = 1     rear = 0      //  此时是真的满了,这种情况与 初始状态一样,所以无法区分满和空。

所以我们退而求其次,使用 “临界状态” 认为 “满的状态”, 即  (rear+2)%(k+1) == front

认为 初始状态,也就是空的状态,即 (rear+1)%(k+1) == front ,这种整体的利用率为 k/(k+1),当k趋于无穷大时候,利用率为100%,我们用C语言实现一下,以下代码通过了LeetCode,请放心食用。

typedef struct {
    int front;
    int rear;
    int size;
    int *arr;    
} MyCircularQueue;
bool myCircularQueueIsEmpty(MyCircularQueue* obj);
bool myCircularQueueIsFull(MyCircularQueue* obj);
MyCircularQueue* myCircularQueueCreate(int k) {
    MyCircularQueue* p = malloc(sizeof(MyCircularQueue));
    p->size = k + 1;
    p->arr = malloc(sizeof(int)*(k+1));
    p->front = 1;
    p->rear = 0;
    return p;
}

bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
  if (myCircularQueueIsFull(obj)){
      return false;
  }else {
      obj->rear = (obj->rear + 1)%(obj->size);
      *(obj->arr + obj->rear) = value;
      return true;
  }
}

bool myCircularQueueDeQueue(MyCircularQueue* obj) {
  if (myCircularQueueIsEmpty(obj)){
      return false;
  }else {
      obj->front = (obj->front + 1)%(obj->size);
      return true;
  }
}

int myCircularQueueFront(MyCircularQueue* obj) {
  if (myCircularQueueIsEmpty(obj)){
      return -1;
  }else {
      return *(obj->arr + obj->front);
  }
}

int myCircularQueueRear(MyCircularQueue* obj) {
  if (myCircularQueueIsEmpty(obj)){
      return -1;
  }else {
      return *(obj->arr + obj->rear);
  }
}

bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
    if ( (obj->rear + 1)%(obj->size) == obj->front ){
        return true;
    }else{
        return false;
    }
  
}

bool myCircularQueueIsFull(MyCircularQueue* obj) {
    if ( (obj->rear + 2)%(obj->size) == obj->front ){
        return true;
    }else{
        return false;
    }
}

void myCircularQueueFree(MyCircularQueue* obj) {
    free(obj->arr);
    free(obj);
}

总结及注意事项

1.  这题本质上是有两个不同的解决方法,其中一个是 保存个数来计算空和满,另一个是 按照公式来计算空和满。 题解二和题解三,只是rear指向不一样,这样他们的代码也会不一样。

其实 我推荐 题解三,无需引入count,且 front和rear均指向首尾元素,只是多利用了一个空间而已,k/(k+1) 约等于 100%, 缺点是需要记住公式,可以逆推。

如果front和rear均指向首尾元素,如果当队列只有一个元素,那么 front == rear,如果此时出队这个元素,front++,出现  front > rear 且 front -1 = rear,此时 队列为空。

我们假设 rear = 0 ,那么 front =1 来代表 空队列,即 (rear +1)%( obj->size) == front

此时我新增 k 个元素,得到 rear = k, 再增加一个元素,得到 rear = 0,此时无法与空队列进行区分,所以

满队列则为 rear = k  , front = 1  来代表 满队列,   即 ( rear + 2) % (obj->size ) == front

2. 如果用C语言来写,一定注意要先声明 满和空的函数,否则无法在其他函数里面使用。

bool myCircularQueueIsEmpty(MyCircularQueue* obj);
bool myCircularQueueIsFull(MyCircularQueue* obj);

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

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

相关文章

chatgpt赋能python:Python安装好后怎么写代码?

Python安装好后怎么写代码&#xff1f; Python是一种高级编程语言&#xff0c;已成为众多开发者的首选工具。根据一些统计数据&#xff0c;Python排名全球第三的流行语言&#xff0c;已经成为Web开发、数据科学和人工智能领域的首选语言。如果您刚刚安装了Python&#xff0c;那…

【LeetCode全题库算法速练】6、N 字形变换

文章目录 一、题目&#x1f538;题目描述&#x1f538;样例1&#x1f538;样例2&#x1f538;样例3 二、代码参考 作者&#xff1a;KJ.JK &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &a…

YOLOv5/v7 添加注意力机制,30多种模块分析①,SE模块,SK模块

目录 一、注意力机制介绍1、什么是注意力机制&#xff1f;2、注意力机制的分类3、注意力机制的核心 二、SE模块1、SE模块的原理2、代码实例3、实验结果4、应用示例&#xff08;1&#xff09;在 models/yolo.py 文件中定义 SEModule 类&#xff0c;用于实现SE模块。&#xff08;…

pyautogui实现自动连接GP VPN

支线小小项目(pyautogui实现自动连接GP VPN) 用了pyautogui做了一个懒人小脚本&#xff0c;主要是在家上班&#xff0c;每天要连公司vpn, 然后还要等好久&#xff0c;公司用的GP(global protect) VPN, 长这个样子 主要问题每次点击connect后需要等他先出来windows NT的login认…

Web安全总结

目录 网站架构一般web服务器结构相比于传统的网络攻击&#xff0c;基于web的攻击有什么不同&#xff1f;HTTP协议HTTP响应拆分攻击HTTPS针对HTTPS协议的攻击那么如何保证证书的唯一性&#xff1f; HTTP会话Cookie和Session的关系HTTP会话攻击解决方案 Web访问中的隐私问题Web应…

【读书笔记】《贫穷的本质》- [印度] Abhijit Banerjee / [法] Esther Duflo

文章目录 前言第一章 再好好想想第一部分 生活案例第二章 饥饿人口已达到10亿&#xff1f;第三章 提高全球居民健康水平容易吗&#xff1f;第四章 全班最优 前言 扶贫政策方面充斥着会取得立竿见影的效果的泡沫&#xff0c;事实证明这一点儿也不奇怪。要想取得进展&#xff0c;…

sed:命令讲解一

sed的使用&#xff1a; sed的使用 一、sed1.定义&#xff1a;2.工作流程&#xff1a;读取&#xff0c;执行&#xff0c;显示。3.基本操作格式&#xff1a;4.sed操作符&#xff1a;5.扩展&#xff1a; 一、sed 1.定义&#xff1a; 一种流编辑器&#xff0c;会在编辑器处理数据…

浅谈发改委强化电力需求侧管理缓解电力系统峰值压力方案设计 安科瑞 许敏

摘要&#xff1a;近年来全国用电负荷特别是居民用电负荷的快速增长&#xff0c;全国范围内夏季、冬季用电负荷“双峰”特征日益突出&#xff0c;恶劣气候现象多发增加了电力安全供应的压力。具有随机性、波动性、间歇性特征的可再生能源大规模接入电网对电力系统的稳定性带来新…

MySQL 索引及查询优化总结

一个简单的对比测试 前面的案例中&#xff0c;c2c_zwdb.t_file_count表只有一个自增id&#xff0c;FFileName字段未加索引的sql执行情况如下&#xff1a; 在上图中&#xff0c;typeall&#xff0c;keynull&#xff0c;rows33777。该sql未使用索引&#xff0c;是一个效率非常低…

chatgpt赋能python:Python安装HanLP:一个强大的NLP工具

Python安装HanLP&#xff1a;一个强大的NLP工具 HanLP是一个基于Python编写的神经网络自然语言处理工具&#xff0c;它提供给我们强大的文本处理和分析能力。在这篇文章中&#xff0c;我们将介绍如何在Python中安装并使用HanLP工具。如果你是一个文本处理和分析的爱好者或者工…

chatgpt赋能python:Python安装到U盘——实现随时随地的编程

Python安装到U盘——实现随时随地的编程 Python是一种广泛使用的动态解释型编程语言&#xff0c;简单易学&#xff0c;适用性广泛&#xff0c;被广泛应用于数据分析、Web开发、人工智能等领域。想要充分发挥Python的优势&#xff0c;随时随地进行编程&#xff0c;我们可以将Py…

人工智能正迎来量子飞跃——

光子盒研究院 6月1日&#xff0c;量子计算领域的行业领导者IonQ公布了其应用量子计算机模拟人类认知的一项早期研究结果。这篇论文描述了世界上第一个公开的方法&#xff1a;研究团队已将一个基本的人类认知模型在量子硬件上运行&#xff0c;这为模仿人类思维方式的改进决策模型…

Day_42哈希表

目录 一. 关于哈希表 二. 如何实现哈希表 1. 散列函数 2. 散列表 3. 散列函数的构造方法 4. 处理冲突的方法 三. 代码实现 1. 构造函数构造哈希表 2. 哈希表的查找 四. 代码展示 五. 数据测试​编辑 六. 总结 一. 关于哈希表 在前面介绍的线性表的查找中,记录在表中的位置…

RabbitMQ入门案例之Simple简单模式

RabbitMQ入门案例之Simple简单模式 前言什么是Simple模式Simple模式操作RabbitMQ管理界面的部分介绍 前言 本文将介绍RabbitMQ的七种工作模式的第一种Simple模式的代码实现&#xff0c;编程工具使用的是IDEA&#xff0c;在RabbitMQ中的工作模式都是生产消费模型 生产者消费模型…

Android系统的Ashmem匿名共享内存子系统分析(4)- Ashmem子系统的 Java访问接口

声明 其实对于Android系统的Ashmem匿名共享内存系统早就有分析的想法&#xff0c;记得2019年6、7月份Mr.Deng离职期间约定一起对其进行研究的&#xff0c;但因为我个人问题没能实施这个计划&#xff0c;留下些许遗憾…文中参考了很多书籍及博客内容&#xff0c;可能涉及的比较…

【云原生】Docker镜像的创建

1.Dokcer镜像的创建 创建镜像有三种方法&#xff0c;分别为【基于已有镜像创建】、【基于本地模板创建】以及【基于Dockerfile创建】。 1.1 基于现有镜像创建 &#xff08;1&#xff09;首先启动一个镜像&#xff0c;在容器里做修改 docker run -it --name web centos:7 /…

2023年6月 国内大语言模型对比【国内模型正在崛起】

先说一下这个文章怎么来的。因为朋友问我大语言模型可以生成公务员面试回答不&#xff0c;我说可以啊。之前看文心有这个服务。我想最近好几个模型也没用了测一把&#xff01;结果&#xff01;大吃一惊&#xff01;我觉得我的三个傻孩子长大了&#xff01;&#xff08;chatglm1…

chatgpt赋能python:Python如何降低版本:提升代码兼容性与SEO效果

Python如何降低版本&#xff1a;提升代码兼容性与SEO效果 在大多数情况下&#xff0c;使用Python的最新版本是最好的选择。新版本通常提供更好的性能和更多的功能&#xff0c;同时也有更好的安全性和稳定性。然而&#xff0c;有些情况下&#xff0c;我们需要运行旧版本的Pytho…

chatgpt赋能python:Python字体如何调大?一篇全面的教程

Python字体如何调大&#xff1f;一篇全面的教程 什么是Python&#xff1f; Python是一种高级编程语言&#xff0c;一般被用于Web开发、数据分析和人工智能等领域。其编写简单、易读易学易维护&#xff0c;因此被广泛使用。 为什么要调大Python字体&#xff1f; 在Python编程…

imagine 关键词绘图( Midjourney )

前几天从网上看到的imagine 关键词绘图( Midjourney )&#xff0c;感觉挺好用&#xff0c;分享给大家&#xff1a; 一、基本关键词: 一个基本的提示可以简单到一个单词、短语或表情符号。 二、高级关键词: 可以包括一个或多个图像链接、多个文本短语或单词&#xff0c;以及…