设计循环队列(c语言)

前言

在上一篇文章中我们了解了关于循环队列的基本特性:

d20ac53cf2374538ab6e197201cdc6d3.png

1、当rear == front时,表示队列为空

2、当rear + 1 = front时,表示队列已满

        当我们需要实现循环队列时,通常会选择使用链表或数组来存储队列中的元素。而使用数组来实现循环队列是一种更高效和方便的方法。

        事实上,数组是没有办法像上图所示的那样围成一个圈,它的实际情况应该是这样的:

back和front表示的是数组下标而非指针

75157fbc2567455e80e97e0ee79f27f9.png

        我们虽然可以通过back == front判断循环队列为空,但是当push 4后,由于是循环队列所以此时back会回到front的位置,这样就与back == front为循环队列空的判断条件冲突,所以该如何区分队列为空还是为满?

7035c3481132496bac7a52de00921c19.png

为此我们想到了多开辟一个空间的操作(该空间也可以用于存放数据),即可以存放的数据个数为k个则开辟空间个数为k+1个:

74b64b3f72474a1d8c95565c723e9970.png

多开辟的内个空间起到一个暂时中转的作用 

这样就解决back无处安放的窘境,此时由于空间已满,故开始出队,没出队一个元素front++:

73b560ec765f45379d842ae710bec17a.png

有了多余空间后再进行入队操作,此时back++后还要令back = back % (k+1):

59bc0d0f2ec94110998284a397fd20d8.png

上面提到的取模问题是该题目的最大难点,之所以要进行取模运算就是为了让back可以回到原来的位置处然后入队,我们拿具体的数字来让大家更好的理解:

假设此时队列中能且已经存放了四个有效数据,back此时的位置应该位于我们为其开辟的中转位置处值为4(因为我们要用back的值来表示每一次入队时的下标,所以每当入队一次后back都要++一次为了下一次的元素入队做准备)然后我们选择两次出队pop掉了1和2,接着我们push 5时,就应该向额外开辟的空间中push 5,此时back++后值为5,但是已知两次出队后在前面空出来两个空间(即使是一个也可以)且队列为循环队列,所以这时候就应该将back移到前面去,让back的值变为0(利用数组的循环特性),而我们只能利用取模运算%来达到这一目的(在字符逆序问题中我们也是用%来解决无效旋转问题)

关于数组的循环特性的解释:

        数组具有固定大小和连续内存空间的特点。当队列元素达到数组末尾时,如果还有剩余空间,我们希望能够利用这些空间继续存储新元素。通过取模运算可以将尾指针重新回到数组开头位置,使得队列成为一个闭合回路

——————————下面我们正式写一道关于如何设计循环队列的OJ题——————————

622. 设计循环队列 - 力扣(LeetCode)

1、初始化循环队列

  1. 声明一个包含数组、front下标、back下标、以及数组元素个数信息的结构体
  2. 为该结构体申请一个内存空间
  3. 为数组申请k+1个内存空间
  4. 初始化front下标和back下标为0、数组元素个数为k(该函数的形参为k)
  5. 返回申请的结构体地址
typedef struct {
    int* a;
    int front;  //front和back均表示下标
    int back;
    int k;      //这里k我们表示
} MyCircularQueue;

//初始化循环队列
MyCircularQueue* myCircularQueueCreate(int k) {
    MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    obj->a = (int*)malloc(sizeof(int)*(k+1));
    obj->front = 0;
    obj->back = 0;
    obj->k = k;

    return obj;
}

2、判断循环队列是否为空

  1. 只需要利用判断front == back的结果是真是假即可(循环队列特性)
//判断循环队列是否为空
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
    return obj->front == obj->back;
}

3、判断循环队列是否已满

  1. 想象中判断已满应该是back+1 == front,它所传达的意思是当back表示下一个位置的下标的值等于front表示的下标的值相同时循环队列已满,但是我们在这里不能简单的这样写,由于我们利用数组的循环特性以及取模运算,所以要计算出back的下一个位置的下标的值就应该用(back+1后的值)% (实际的空间大小即k+1),然后将结果于front表示的下标的值比较
//判断循环队列是否已满
bool myCircularQueueIsFull(MyCircularQueue* obj) {
    return (obj->back+1) % (obj->k+1) == obj->front;
}

4、入队操作

  1. 先判断队列是否已满,若满则返回false
  2. 若未满则将要入队的元素的值value放入数组中下标为back的位置
  3. 放入元素后back++为下一次入队操作做准备
  4. 最后为了形成真正意义上的循环队列,需要利用%判断back的值是否需要从头开始,比如如果此时back的值等于开辟的空间数量,即back % (k+1) == 0,证明此时back需要从头开始了,若back<k+1,由于取模运算的性质back的值并不会发生改变,而back的值不会大于k+1 
//入队操作,失败返回假,成功返回真
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
    if(myCircularQueueIsFull(obj))
        return false;
    
    obj->a[obj->back] = value;

    obj->back++;
    obj->back %= (obj->k+1);
    //上两步操作相当于:obj->back =(obj->back+1) % (obj->k+1);

    return true;
}

5、出队操作

  1. 先判断队列是否为空,若空则返回false
  2. 若不为空则将front的值++,即front表示的下标的值增大(->的优先级大于++)
  3. 最后为了形成真正意义上的循环队列,需要利用%判断front的值是否需要从头开始,比如如果此时front的值等于开辟的空间数量,即front % (k+1) == 0,证明此时front需要从头开始了,若front<k+1,由于取模运算的性质front的值并不会发生改变,而front的值不会大于k+1
//出队操作,失败返回假,成功返回真
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
    if(myCircularQueueIsEmpty(obj))
        return false;

    ++obj->front;
    obj->front %= (obj->k+1);
    
    return true;
}

6、获取队头元素

  1. 先判断队列是否为空,若空则返回-1(题目要求此时应返回-1)
  2. 若不为空则直接返回下标值为front的数组元素
//获取队头元素
int myCircularQueueFront(MyCircularQueue* obj) {
    if(myCircularQueueIsEmpty(obj))
        return -1;

    return obj->a[obj->front];
}

7、获取队尾元素

//获取队尾元素
int myCircularQueueRear(MyCircularQueue* obj) {
    if(myCircularQueueIsEmpty(obj))
        return -1;

    //最初版本:
    //return obj->a[(obj->back - 1 + obj->k + 1) % (obj->k+1)];
    //简化版本:
    return obj->a[(obj->back + obj->k) % (obj->k+1)];
}
  1.  先判断队列是否为空,若空则返回-1(题目要求此时应返回-1)
  2. 使用back-1是因为back表示的下标的前一个下标才是真正的队尾元素下标,而当back等于0时back-1等于-1,此时队尾元素的下标为4,为了获得该实际下标就需要进行后续的加和取模操作(如果back的值大于零则后面的加和取模运算并不会对实际要获取的下标值产生影响)总之,后续的加法和取模运算都是为了处理back等于0这一特殊情况的

8、销毁循环队列

//销毁队列
void myCircularQueueFree(MyCircularQueue* obj) {
    free(obj->a);
    free(obj);
}
  1. 为谁开辟了空间就销毁谁(平平无奇的销毁操作)

!!注意队列的判空和判满函数需要写在初始化队列的后面,否则报错!!

最终代码 

typedef struct {
    int* a;
    int front;  //front和back均表示下标
    int back;
    int k;      //这里k我们表示
} MyCircularQueue;

//初始化循环队列
MyCircularQueue* myCircularQueueCreate(int k) {
    MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    obj->a = (int*)malloc(sizeof(int)*(k+1));
    obj->front = 0;
    obj->back = 0;
    obj->k = k;

    return obj;
}

//判断循环队列是否为空
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
    return obj->front == obj->back;
}

//判断循环队列是否已满
bool myCircularQueueIsFull(MyCircularQueue* obj) {
    return (obj->back+1) % (obj->k+1) == obj->front;
}

//入队操作,失败返回假,成功返回真
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
    if(myCircularQueueIsFull(obj))
        return false;
    
    obj->a[obj->back] = value;
    obj->back++;
    obj->back %= (obj->k+1);

    return true;
}

//出队操作,失败返回假,成功返回真
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
    if(myCircularQueueIsEmpty(obj))
        return false;

    ++obj->front;
    obj->front %= (obj->k+1);
    
    return true;
}

//获取队头元素
int myCircularQueueFront(MyCircularQueue* obj) {
    if(myCircularQueueIsEmpty(obj))
        return -1;

    return obj->a[obj->front];
}

//获取队尾元素
int myCircularQueueRear(MyCircularQueue* obj) {
    if(myCircularQueueIsEmpty(obj))
        return -1;

    //最初版本:
    //return obj->a[(obj->back - 1 + obj->k + 1) % (obj->k+1)];
    //简化版本:
    return obj->a[(obj->back + obj->k) % (obj->k+1)];
}

//销毁队列
void myCircularQueueFree(MyCircularQueue* obj) {
    free(obj->a);
    free(obj);
}

/**
 * Your MyCircularQueue struct will be instantiated and called as such:
 * MyCircularQueue* obj = myCircularQueueCreate(k);
 * bool param_1 = myCircularQueueEnQueue(obj, value);
 
 * bool param_2 = myCircularQueueDeQueue(obj);
 
 * int param_3 = myCircularQueueFront(obj);
 
 * int param_4 = myCircularQueueRear(obj);
 
 * bool param_5 = myCircularQueueIsEmpty(obj);
 
 * bool param_6 = myCircularQueueIsFull(obj);
 
 * myCircularQueueFree(obj);
*/

~over~

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

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

相关文章

[点云分割] 平面分割

一、介绍 SACSegmentation&#xff08;Sample Consensus Segmentation&#xff09;是PCL中的一个分割算法&#xff0c;用于从点云中识别出具有相同几何形状的模型。该算法使用采样一致性&#xff08;Sample Consensus&#xff09;方法&#xff0c;通过迭代地随机采样一组数据点…

JavaScript的学习之BOM和DOM,就这一篇就OK了!(超详细)

目录 Day28 JavaScript(2) 1、BOM对象 1.1 window对象 1.2 Location ( 地址栏)对象 1.3 本地存储对象 2、DOM对象(JS核心) 2.1 查找标签 2.2 绑定事件 2.3 操作标签 2.4 常用事件 Day28 JavaScript(2) 1、BOM对象 BOM:Broswer object model,即浏览器提供我们开发者…

MySQL索引,你真的学会了?索引底层原理是什么?索引什么时候失效,你知道吗?

目录 1、什么是索引 2、索引分类 3、索引的基本操作 3.1、主键索引 3.2、单列索引 3.3、唯一索引 3.4、复合索引 4、索引的底层原理 为什么使用BTree而不是B-Tree? 如果数据量特别大的情况下&#xff0c;BTree会不会深度太深影响查询效率&#xff1f; 5、聚簇索引和…

新加坡服务器托管-金融企业的选择

新加坡作为一个亚洲金融中心&#xff0c;其优越的地理位置和先进的信息通信技术基础设施&#xff0c;使得其成为了众多金融机构企业选择服务器机房托管的理想地点。金融行业对于服务器的安全性和可靠性要求很高&#xff0c;而将服务器托管在新加坡有许多好处。 首先&#xff0c…

SpringBoot趣探究--1.logo是如何打印出来的

一.前言 从本篇开始&#xff0c;我将对springboot框架做一个有趣的探究&#xff0c;探究一下它的流程&#xff0c;虽然源码看不懂&#xff0c;不过我们可以一点一点慢慢深挖&#xff0c;好了&#xff0c;下面我们来看一下本篇的知识&#xff0c;这个logo是如何打印出来的&#…

Ubuntu设设置默认外放和麦克风设备

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、pulseaudio 是什么&#xff1f;二、配置外放1.查看所有的外放设备2.设定默认的外放设备3.设定外放设备的声音强度4.设定外放设备静音 三、配置麦克风1.查看…

做好性能测试计划的4个步骤!全都是精华!【建议收藏】

如何做好一次性能测试计划呢&#xff1f;对于性能测试新手来说&#xff0c;也许你非常熟悉Jmeter的使用&#xff0c;也许你清楚的了解每一个系统参数代表的意义&#xff0c;但是想要完成好一次性能测试任务&#xff0c;并不仅仅是简单的写脚本&#xff0c;加压力&#xff0c;再…

育种值探秘丨动植物遗传育种

育种值&#xff1a;生物的数字密码 嗨&#xff0c;大家好&#xff01;今天分享的笔记是遗传育种领域中那神秘莫测的育种值。这个抽象的名词具体如何理解&#xff1f;为什么说育种值很重要&#xff1f;具体怎么计算&#xff1f;有什么用处&#xff1f; 别担心&#xff0c;我会用…

万字解析设计模式之桥接模式、外观模式

一、桥接模式 1.1概述 桥接模式是一种结构型设计模式&#xff0c;它的作用是将抽象部分和实现部分分离开来&#xff0c;使它们能够独立地变化。这样&#xff0c;抽象部分和实现部分可以分别进行扩展&#xff0c;而不会相互影响。它是用组合关系代替继承关系来实现&#xff0c;…

Linux:wget后台下载/查看后台任务进度

1. 后台下载 使用wget -b url&#xff1a; wget -b http://cn.wordpress.org/wordpress-3.1-zh_CN.zip后台任务启动后&#xff0c;会返回两段话&#xff0c;第一段返回一个pid&#xff0c;代表这个后台任务的进程&#xff0c;并且我们可以kill掉这个id来终止此次下载&#x…

【Python】给出n个数,找出这n个数的最大值,最小值,和。

问题描述 给出n个数&#xff0c;找出这n个数的最大值&#xff0c;最小值&#xff0c;和。 样例输入 5 1 3 -2 4 5 Data 样例输出 5 -2 11 n int(input()) # 从用户输入中读取一个整数&#xff0c;将其赋给变量n# 从用户输入中读取一行字符串&#xff0c;使用空格分割字符串&a…

LED Driver数码屏应用解决方案

今天给大家介绍的产品是LED Driver&#xff0c;这属于电源管理类芯片&#xff0c;一般分为恒流驱动与恒压驱动&#xff0c;但是常见的就是恒流驱动&#xff0c;能够保持产品在驱动中提供恒定且稳定的电流。 基本概述 TM1629是一种带键盘扫描接口的LED&#xff08;发光二极管显…

线程池[重点]

线程池概述 线程池就是一个可以复用线程的技术。 不使用线程池的问题 &#xff1a;如果用户每发起一个请求&#xff0c;后台就创建一个新线程来处理&#xff0c;下次新任务来了又要创建新线程&#xff0c;而创建新线程的开销是很大的&#xff0c;这样会严重影响系统的性能。 …

2023年中国醇酸树脂涂料需求量、应用领域及市场规模前景分析[图]

醇酸树脂指多元醇和多元酸与脂肪酸经过酯化缩聚生成的高聚物&#xff0c;其由邻苯二甲酸酐、多元醇和脂肪酸或甘油三脂肪酸酯缩合聚合而成。醇酸树脂固化成膜后&#xff0c;具有耐磨性好、绝缘性佳等优势&#xff0c;在涂料领域应用广泛。2022年醇酸树脂产量约336.3万吨&#x…

完全二叉树你需要了解一下

完全二叉树介绍完全二叉树应用场景完全二叉树和满二叉树的区别完全二叉树代码示例拓展 完全二叉树介绍 完全二叉树&#xff08;Complete Binary Tree&#xff09;是一种特殊的二叉树&#xff0c;它的定义是&#xff1a;如果设二叉树的深度为h&#xff0c;除第h层外&#xff0c…

基于白冠鸡算法优化概率神经网络PNN的分类预测 - 附代码

基于白冠鸡算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于白冠鸡算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于白冠鸡优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神经网络…

改进YOLOv8:结合Biformer——基于动态稀疏注意力构建高效金字塔网络架构

🗝️YOLOv8实战宝典--星级指南:从入门到精通,您不可错过的技巧   -- 聚焦于YOLO的 最新版本, 对颈部网络改进、添加局部注意力、增加检测头部,实测涨点 💡 深入浅出YOLOv8:我的专业笔记与技术总结   -- YOLOv8轻松上手, 适用技术小白,文章代码齐全,仅需 …

为什么AirtestIDE的selenium Window突然无法检索控件了?

1. 前言 最近有很多朋友跟我们反馈&#xff0c;为什么1.2.15版本的IDE没办法做网页元素检索了&#xff0c;是不是我们不支持selenium了之类的。 测试后发现&#xff0c;目前版本确实存在这个问题&#xff0c;原因是Chrome113.0.5672.127(最新)版本过高&#xff0c;AirtestIDE…

C语言--输入三角形的三边,输出三角形的面积

一.题目描述 输入三角形的三边&#xff0c;输出三角形的面积。比如&#xff1a;输入三角形的三边长度是3&#xff0c;4&#xff0c;5.输出6 二.思路分析 利用海伦公式可以很好解决 海伦公式的表达式如下&#xff1a; s (a b c) / 2 面积 sqrt((s * (s - a) * (s - b) * (…

基于阶梯碳交易的含P2G-CCS耦合和燃气掺氢的虚拟电厂优化调度matlab程序

微❤关注“电气仔推送”获得资料&#xff08;专享优惠&#xff09; 参考文献&#xff1a; 基于阶梯碳交易的含P2G-CCS耦合和燃气掺氢的虚拟电厂优化调度——陈登勇 主要内容&#xff1a; 以碳交易和碳封存成本、燃煤机组启停和煤耗成本、弃风成本、购气成本之和为目标函数&…