【初阶数据结构和算法】leetcode刷题之设计循环队列

news2024/11/26 9:45:26

在这里插入图片描述

文章目录

  • 一、实现循环队列
    • 1.大致思路分析
    • 2.循环队列的结构定义和初始化
      • 结构定义
      • 初始化
    • 3.循环队列的判空和判满
      • 判空和判满难点分析
      • 判空
      • 判满
    • 4.循环队列的入队列和出队列
      • 入队列
      • 出队列
    • 5.循环队列取队头和队尾元素
      • 取队头元素
      • 取队尾元素
    • 6.循环队列的销毁
    • 7.最后题解源码

一、实现循环队列

题目链接:https://leetcode.cn/problems/design-circular-queue/description/

1.大致思路分析

   我们来看看题目描述:
在这里插入图片描述
   这是leetcode上的一道中等难度的题,一般来说,leetcode上简单的题不一定简单,中等一定很麻烦,这道题虽然核心也不难,但是就是麻烦
   在做这道题之前,我们要先来介绍一下什么是循环队列,循环队列是一种特殊的队列,它的首尾在逻辑上是相连的,但是还是队头出数据,队尾入数据,保持队列先进先出的特性,被称为环形缓冲器
   但是它和我们之前实现的队列的最大不同是,之前我们实现的队列如果容量不够是可以扩容的,是可变的,而循环队列的容量确定之后就不能再更改,比如后面我们写初始化方法时,会给定一个值给我们初始化,然后我们开辟好空间后就不再修改它的大小了,所以循环队列的容量一经确定就不会再更改
   那么我们接下来大致来分析一下循环队列的结构,我们是继续使用实现队列时的链表,还是说使用数组呢?其实两个都可以,只是使用链表需要使用循环链表,那么哪一个结构更优呢?
   循环队列和普通队列的一个较大区别是,循环队列的大小是固定好了的,当我们去删除数据时,不会直接释放空间,而是会留着存储以后新插入的数据,那么很明显数组的优势更大一些
   因为数组数据的删除可以只改变size,比如尾删就是让size- -,这样就间接实现了删除,我们访问不到原本的尾数据了,但是其实那个空间还在,只是我们size- -后访问不到了而已
   但是链表数据的删除会释放节点,如果不释放节点的话让它强行留下来就会很麻烦,但是我们循环队列的大小又是固定的,到时候重新申请节点插入到原位置又很麻烦,并且链表的开销比数组要大,所以综上我们实现循环链表最好使用数组
   由于队列要遵循头删尾入,所以我们最好像定义队列一样,定义两个下标front和rear分别指向循环队列的头和尾,方便进行操作,注意:rear和之前顺序表的size一样,不是指向有效元素中的最后一个元素,而是这个最后一个有效元素的下一个位置
   那么数组怎么实现首尾相连呢?首尾相连就是尾的下一个节点是头,实现这个需要一定的技巧性,要使用模运算,当rear或者front越界时,模上容量大小就可以让它们重新回到开头,就像首尾相连了一样,如图:
在这里插入图片描述

   现在我们大致知道了循环队列的底层存储,接下来就边做题边讲解思路

2.循环队列的结构定义和初始化

结构定义

   现在我们知道了使用数组,所以循环队列中肯定要有一个数组,但是我们不知道数组的具体大小,需要到时候根据初始化函数的参数确定,所以我们这里直接用一个指针代替,到时候动态申请空间
   由于队列要遵循头删尾入,所以我们最好像定义队列一样,定义两个下标front和rear分别指向循环队列的头和尾(这个尾相当于顺序表中的size),方便进行操作,还有就是我们要知道申请了多大空间,所以用一个整型变量capacity来存储
   那么是否要头删使用数组的效率会很低呢?这个是不需要担心的,因为循环队列的空间不需要释放,所以头删只需要让front++即可,到后面出队列再细讲,那么最后循环队列的大致结构定义如下:

typedef struct 
{
    int* arr;
    int capacity;
    int rear;
    int front;
} MyCircularQueue;

初始化

   在初始化函数中给了我们一个参数k,就是我们要申请的空间大小,我们直接通过动态申请k个整型(后面可能会需要更改,我们先实现到这里),然后将容量置为k,两个指向头和尾的下标置为0,如下:

MyCircularQueue* myCircularQueueCreate(int k) 
{
    MyCircularQueue* pq = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    if(pq == NULL)
    {
        perror("malloc");
        return NULL;
    }
    //后面这里可能会进行改动,这里做一个标记
    pq->arr = (int*)malloc(sizeof(int) * k);
    if(pq->arr == NULL)
    {
        perror("malloc");
        return NULL;
    }
    pq->rear = pq->front = 0;
    pq->capacity = k;
    return pq;
}

3.循环队列的判空和判满

判空和判满难点分析

   循环队列的难点不是后面的入队列和出队列,而是这个部分的判空和判满,因为我们会发现队列空时front和rear相等,队列满时front和rear也相等了,它们难以区分了,如图:
在这里插入图片描述
   可以看到队列空时,front和rear相等,指向头部,那么如果队列满了呢?如图:
在这里插入图片描述
   这里的rear相当于是之前顺序表中的size,指向有效数据的下一个位置,那么现在队列满了,rear指向元素5的下一个位置,同时元素5是最后一个元素,所以根据循环首尾相连的特点,它需要指向头
   那么怎么让它回到头呢?采用的方法就是让rear%capacity,这个方法可以自动帮我们去将越界的rear放回到头,但是问题就来了,此时front和rear又相等了,那么就说明front和rear相等可能是队列为空,也可能是队列满了
   怎么解决呢?我们这样做,在开辟空间时,我们不再开辟k个空间,而是开辟k+1个空间,多开辟一个空间,有什么作用呢?我们画画图就知道了:
在这里插入图片描述
在这里插入图片描述

   是不是非常巧妙呢?但是问题又来了,当空间满了之后,front和rear不相等了,我们如何判断队列是否满了呢?
   其实也不难,我们多开了一个空间,那么实际的空间就应该是capacity+1,实际rear也要+1,所以判断循环队列空间是否为满的条件就是front等于(rear+1) % (capacity+1),现在我们简单画个图来解释,后面还有其它情况到时候再说,如下:
在这里插入图片描述
   那么现在我们分析好了之后,我们还要做一件事,就是之前我们只开辟了k个空间,现在我们要把它改成k+1个空间,其它不变,如下:

    pq->arr = (int*)malloc(sizeof(int) * (k+1));

   这个做完之后我们就可以直接来实现判空和判满了

判空

   判空的条件就是循环队列的front等于rear,如下:

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

判满

   判满的条件就是循环队列 (rear + 1) % (capacity + 1) == front,如下:

bool myCircularQueueIsFull(MyCircularQueue* obj) 
{
    return (obj->rear + 1) % (obj->capacity + 1) == obj->front;
}

4.循环队列的入队列和出队列

   我们先来看看题目给出入队列和出队列操作的要求,如图:
在这里插入图片描述
   题目的意思是如果我们插入或删除成功需要返回true,虽然题目没有说,但是我们应该都能想到插入和失败是不是就要返回false了,所以我们在入队列和出队列之前要记得判断能否插入和删除数据,那么接下来我们进入对队列和出队列的具体分析

入队列

   在入队列之前,我们首先要判断循环队列能否入队列,如果它满了,说明我们不能再插入数据了,就要直接返回false
   判断完之后我们就要真正来实现入队列操作了,入队列就是往数组的最后插入数据,也就是往rear的位置插入数据,插入完之后让rear++
   但是我们要注意一个点,rear++之后可能会越界,我们要让它模上数组真正大小,也就是capacity+1,让它重新走到开头,如图:
在这里插入图片描述
在这里插入图片描述
   当然,当我们插入完数据后,不要忘记返回true,如下:

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

出队列

   在出队列之前,我们首先要判断循环队列能否出队列,如果它为空,说明我们不能再删除数据了,就要直接返回false
   判断完之后我们就要真正来实现出队列操作了,出队列就是删除数组头的数据,但是由于我们不需要释放空间,并且这是数组,所以我们可以直接让front++,间接实现头删,如图:
在这里插入图片描述
   但是同样有一个问题,那就是front++后也可能越界,比如上面的例子中,如果再入队列几次,再去出队列,front++,是不是就刚好越界了,所以front也是有可能越界的
   为了防止越界,让循环队列首尾相连,也只需要让front % (capacity + 1)即可,跟上面入队列是rear的解决方式一样,这里就不多说了,有了思路之后我们直接来写代码,如下:

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

5.循环队列取队头和队尾元素

   在实现取队头和队尾元素操作之前,我们来看看题目的要求:
在这里插入图片描述
   题目提示我们,如果循环队列为空是不是就不能取到队头和队尾元素了,所以如果我们判断出来循环队列为空,需要直接返回-1

取队头元素

   在取队头元素之前我们需要判断循环队列是否为空,为空就返回-1,不为空我们才去取队头元素,方法也很简单,就是直接返回front位置的数据即可,如下:

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

取队尾元素

   在取队尾元素之前我们需要判断循环队列是否为空,为空就返回-1,不为空我们才去取队尾元素,但是取队尾元素会麻烦一点,因为rear不是指向最后一个有效数据,而是指向有效数据的下一个位置
   那么是不是直接返回rear-1位置的数据就可以了呢?大部分情况下是这样的,但是有一种例外,就是当rear = 0时,如果直接-1的话就变成了-1了,越界了,而它的前一个位置应该是原队列中最后一个元素,如图:
在这里插入图片描述
   所以我们可以用重新定义一个prev,让它等于rear-1,如果prev < 0,我们就让它加上循环队列的真实大小(capacity+1),在上图中rear-1为-1,加上真实大小6就变成了5,成功回到了最后一个位置
   处理完prev可能小于0的情况后,就可以直接返回数组中prev位置的数据的,如下:

int myCircularQueueRear(MyCircularQueue* obj) 
{
    if(myCircularQueueIsEmpty(obj))
    {
        return -1;
    }
    int prev = obj->rear-1;
    if(prev < 0)
    {
        prev += obj->capacity + 1;
    }
    return obj->arr[prev];
}

6.循环队列的销毁

   循环队列的销毁是最简单的,只要循环队列底层的数组不为空,就将它的空间释放掉,最后释放掉整个循环队列,如下:

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

7.最后题解源码

typedef struct 
{
    int* arr;
    int capacity;
    int rear;
    int front;
} MyCircularQueue;


MyCircularQueue* myCircularQueueCreate(int k) 
{
    MyCircularQueue* pq = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    if(pq == NULL)
    {
        perror("malloc");
        return NULL;
    }
    pq->arr = (int*)malloc(sizeof(int) * (k+1));
    if(pq->arr == NULL)
    {
        perror("malloc");
        return NULL;
    }
    pq->rear = pq->front = 0;
    pq->capacity = k;
    return pq;
}

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

bool myCircularQueueIsFull(MyCircularQueue* obj) 
{
    return (obj->rear + 1) % (obj->capacity + 1) == obj->front;
}

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

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

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

int myCircularQueueRear(MyCircularQueue* obj) 
{
    if(myCircularQueueIsEmpty(obj))
    {
        return -1;
    }
    int prev = obj->rear-1;
    if(prev < 0)
    {
        prev += obj->capacity + 1;
    }
    return obj->arr[prev];
}

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

   那么今天的刷题就分享到这里,整个栈和队列的题就暂时分享到这里,有什么疑问欢迎提出,后面我们就进入二叉树的学习啦,敬请期待吧!
   bye~

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

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

相关文章

llama-factory 系列教程 (七),Qwen2.5-7B-Instruct 模型微调与vllm部署详细流程实战

文章目录 介绍llama-factory 安装装包下载模型 微调模型数据集训练模型 微调后的模型推理 介绍 时隔已久的 llama-factory 系列教程更新了。本篇文章是第七篇&#xff0c;之前的六篇&#xff0c;大家酌情选看即可。 因为llama-factory进行了更新&#xff0c;我前面几篇文章的实…

矩阵的重复

重复时自身也算一次重复 r e p m a t ( r e p e a t repmat(repeat repmat(repeat m a t l a b ) matlab) matlab)重复矩阵函数 ( ( ( 对矩阵整体 ) ) ) r e p m a t ( a , m , n ) repmat(a,m,n) repmat(a,m,n)将矩阵纵向重复 m m m次&#xff0c;横向重复 n n n次 r e …

【三维重建】windows10环境配置tiny-cuda-nn详细教程

1. 前言 本人在复现water-splatting时&#xff0c;需要配置tiny-cuda-nn&#xff0c;与此同时&#xff0c;出现了很多问题&#xff0c;在此进行简单概述。 2.安装Pytorch 环境版本要求保持一致&#xff1a;CUDA(物理机)&#xff0c;Pytorch&#xff0c;CUDA Toolkit 注意这里…

Js-函数-03

函数定义 在java中我们为了提高代码的复用性&#xff0c;可以使用方法。同样&#xff0c;在JavaScript中可以使用函数来完成相同的事情。JavaScript中的函数被设计为执行特定任务的代码块&#xff0c;通过关键字function来定义。 <!DOCTYPE html> <html lang"en…

MySQL45讲 第29讲 如何判断一个数据库是不是出问题了?——阅读总结

文章目录 MySQL45讲 第二十九讲 如何判断一个数据库是不是出问题了&#xff1f;——阅读总结一、检测数据库实例健康状态的重要性二、常见检测方法及问题分析&#xff08;一&#xff09;select 1 判断法&#xff08;二&#xff09;查表判断法&#xff08;三&#xff09;更新判断…

IO多路复用(Linux epoll)

文章目录 一、IO多路复用介绍1. 缓存 I/O (各种IO模型缘起) 二、目前有哪些IO多路复用的方案三、关联基础知识1. 用户空间和内核空间2. 文件描述符fd 四、Linux IO多路复用 select五、Linux IO多路复用 epoll1. epoll 介绍2. epoll只提供三个函数ET模式与LT模式 3. demo验证 六…

【数据结构与算法】相交链表、环形链表(判断是否有环)、环形链表(返回入环节点)

主页&#xff1a;HABUO&#x1f341;主页&#xff1a;HABUO &#x1f341;如果再也不能见到你&#xff0c;祝你早安&#xff0c;午安&#xff0c;晚安&#x1f341; 1.相交链表 题目&#xff1a;给你两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表…

Move 合约部署踩坑笔记:如何解决 Sui 客户端发布错误Committing lock file

Move 共学活动&#xff1a;快速上手 Move 开发 为了帮助更多开发者快速了解和掌握 Move 编程语言&#xff0c;Move 共学活动由 HOH 社区、HackQuest、OpenBuild、KeyMap 联合发起。该活动旨在为新手小白提供一个良好的学习平台&#xff0c;带领大家一步步熟悉 Move 语言&#…

【C语言】野指针问题详解及防范方法

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C语言 文章目录 &#x1f4af;前言&#x1f4af;什么是野指针&#xff1f;&#x1f4af;未初始化的指针代码示例问题分析解决方法 &#x1f4af;指针越界访问代码示例问题分析解决方法 &#x1f4af;指向已释放内存的…

关于如何在k8s中搭建一个nsfw黄图鉴定模型

随着现在应用内图片越来越多&#xff0c;安全审查也是必不可少的一个操作了 下面手把手教你如何将huggingface中的黄图检测模型部署到自己的服务器上去 1.找到对应的模型 nsfw_image_detection 2.在本地先验证如何使用 首先安装transformers python库 pip install transform…

初学 flutter 环境变量配置

一、jdk&#xff08;jdk11&#xff09; 1&#xff09;配置环境变量 新增&#xff1a;JAVA_HOMEC:\Program Files\Java\jdk-11 //你的jdk目录 在path新增&#xff1a;%JAVA_HOME%\bin2&#xff09;验证是否配置成功&#xff08;cmd运行命令&#xff09; java java -version …

信息安全实验--密码学实验工具:CrypTool

1. CrypTool介绍&#x1f4ad; CrypTool 1的开源教育工具&#xff0c;用于密码学研究。通过CrypTool 1&#xff0c;可以实现加密和解密操作&#xff0c;数字签名。CrypTool1和2有很多区别的。 来源于&#xff1a;网络安全快速入门5-密码学及密码破解工具CrypTool实战_百度知道…

服务器数据恢复—raid5阵列+LVM+VXFS数据恢复案例

服务器存储数据恢复环境&#xff1a; 某品牌MSA2000FC存储中有一组由7块盘组建的RAID5阵列&#xff0c;另外还有1块硬盘作为热备盘使用。 基于RAID5阵列划分的几个LUN分配给小机使用&#xff0c;存储空间通过LVM管理&#xff0c;重要数据为Oracle数据库及OA服务端。 服务器存储…

基于微信小程序的酒店客房管理系统+LW示例参考

1.项目介绍 系统角色&#xff1a;管理员、员工、普通用户功能模块&#xff1a;员工管理、用户管理、客房管理、预订管理、商品管理、评价管理、续订管理、订单管理等技术选型&#xff1a;SSM&#xff0c;vue&#xff0c;uniapp等测试环境&#xff1a;idea2024&#xff0c;jdk1…

学习Zookeeper

Zookeeper有手就行 1. 初识ZooKeeper1.1 安装ZooKeeper1.2 ZooKeeper命令操作1.2.1 Zookeeper数据模型1.2.2 Zookeeper 服务端常用命令1.2.3 Zookeeper客户端常用命令 2. ZooKeeperJavaAPl操作2.1 Curator介绍2.2 CuratorAPI常用操作2.2.0 引入Curator支持2.2.1 建立连接2.2.2 …

java基础知识(Math类)

引入&#xff1a;Math 类包含用于执行基本数学运算的方法&#xff0c;如初等指数、对数、平方根 import java.util.Math 1.abs绝对值 int abs Math.abs(-9); 2.pow求幂 double pow Math.pow(2,4); 3.向上取整 double ceil Math.ceil(3.9);//ceil 4 4.向下取整 dou…

【AIGC】大模型面试高频考点-RAG中Embedding模型选型

【AIGC】大模型面试高频考点-RAG中Embedding模型选型 &#xff08;一&#xff09;MTEB排行榜英文模型排名&#xff1a;1、bge-en-icl2、stella_en_1.5B_v53、SFR-Embedding-2_R4、gte-Qwen2-7B-instruct5、stella_en_400M_v56、bge-multilingual-gemma27、NV-Embed-v18、voyage…

学习threejs,使用设置normalMap法向量贴图创建更加细致的凹凸和褶皱

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

SAP ME2L/ME2M/ME3M报表增强添加字段

SAP ME2L/ME2M/ME3M报表增强添加字段&#xff08;包含&#xff1a;LMEREPI02、SE18:ES_BADI_ME_REPORTING&#xff09; ME2L、ME2M、ME3M这三个报表的字段增强&#xff0c;核心点都在同一个结构里 SE11:MEREP_OUTTAB_PURCHDOC 在这里加字段&#xff0c;如果要加的字段是EKKO、…

dubbo-go框架介绍

框架介绍 什么是 dubbo-go Dubbo-go 是 Apache Dubbo 的 go 语言实现&#xff0c;它完全遵循 Apache Dubbo 设计原则与目标&#xff0c;是 go 语言领域的一款优秀微服务开发框架。dubbo-go 提供&#xff1a; API 与 RPC 协议&#xff1a;帮助解决组件之间的 RPC 通信问题&am…