当前位置: 首页 > article >正文

【练习题】设计循环队列

循环队列

  • 一、题目目的
  • 二、解题思路
    • 用链表设计循环队列
      • 代码实现
      • 判断队列是否为空
      • 判断队列是否为满
      • 插入数据
      • 删除数据
      • 返回队首数据
      • 返回队尾数据
      • 队列销毁
      • 代码整体
    • 用数组设计循环队列
      • 判断队列是否为空
      • 判断队列是否为满
      • 插入数据
      • 删除数据
      • 返回队尾数据
      • 返回队首数据
      • 队列销毁
      • 代码整体
  • 三、总结

一、题目目的

循环队列题目
在这里插入图片描述
题目的要求是我们要设计一个队列,这个队列的空间是固定的,我们能对队列进行插入删除操作。

二、解题思路

我们可以用两种方式设计循环队列
第一种:链表
我们可以用链表来设计循环队列,优点是我们不用 malloc 开辟空间,在也不用 realloc 调整空间大小,并且也不必担心空间浪费,不会出现多开空间而用不上的情况。并且我们可以在任意位置插入或删除数据,只用改变链表的指向就行。
第二种:数组
我们也可以用数组来设计循环队列,优点是因为有数组下标的存在,我们在数组中想要查询某一位置的数据时会很方便。

综上所述
>我们发现链表的优点在于空间利用率高,并且在某一位置插入或删除数据时也很方便。但对于这个题来说,我们要设计一个固定空间大小的队列进行循环,并且因为我们设计的是队列,我们只需要对头和尾的数据进行操作,那么链表的优势我们并用不上。
>而数组的下标查找对我们这个题很友好,我们可以通过数组的下标,很轻松的访问到我们需要的数据,而链表就需要遍历。

(ps : 我们假设题目中的 k = 4,也就是开辟的循环队列中可以存储四个数据。)

用链表设计循环队列

我们在实现队列时我们用的是链表,因为链表的头删和尾插操作很方便,那我们想这个循环队列我们可不可以用链表来进行操作。
若用链表来操作,我们可以将链表头尾相连,使链表成环。
在这里插入图片描述
为了完成题目中循环的要求我们设计两个指针

phead : 作为头指针,用以保存头节点的地址
ptail : 作为尾指针,用以保存尾节点的下一个节点

1>插入数据:
在这里插入图片描述
我们只要将数据插入ptail 所在节点,然后再将ptail 指向它的下一个。

2>删除数据:
在这里插入图片描述

我们只需要将phead 指向它的下一个节点,那么当ptail 再次走到这里要插入数据时,直接覆盖就行。

我们要进行插入或者删除操作时要对队列判空和判满
当队列满时我们不能插入数据
当队列空时我们不能删除数据

那么就引出一个问题,就是如何判空判满,当 phead == ptail 时
有可能是空,也有可能是满。

在这里插入图片描述
所以我们有一些解决方法

eg1:计数器
我们可以在创建队列的结构体是,在里面加一个变量size用于计数,当size = k 时,为满,当size = 0时为空。
eg2:多开辟一个空间
多开辟一个空间,以此来区别空和满。

我采取的是多开辟一个空间的方法,

在这里插入图片描述
当我们有多的一个空间时,我们的满和空就可以区分出来。
当 phead == ptail 时为空
当 ptail->next == phead 时为满。

代码实现

好了思路完成了,接下来就要实现代码了
链表实现的一个麻烦之处就是,我们创建队列。
首先我们要创建一个链表,这个链表中包含了 k + 1 个节点,并且要将他们链接起来。
力扣可能在他的库函数中有 ListNode 这个结构体,所以我就少了一个e,要不然会弹出类型重定义
在这里插入图片描述

再下面,因为我们这个是循环链表,在后面插入删除什么的也不会有空的风险,就很轻松了。

判断队列是否为空

在这里插入图片描述

判断队列是否为满

在这里插入图片描述

插入数据

在这里插入图片描述
在插入之前我们要先判断队列是否为满,然后将数据插在ptail位置,再将ptail向后一个节点

删除数据

在这里插入图片描述
在删除之前我们要先判断队列是否为空,然后直接将phead向后走一个节点,这样原来的节点就相当于我们还给队列了,数据还在,但是当后面插入时,这个节点会被改变。

返回队首数据

在这里插入图片描述
返回之前我们要判断队列是否为空,然后返回phead节点处的数据。

返回队尾数据

在这里插入图片描述
在返回之前要判断队列是否为空,然后遍历队列找到尾节点因为ptail是保存尾节点的下一个节点的,所以需要遍历找到ptail前一个节点
在这里插入图片描述

队列销毁

在这里插入图片描述

在销毁时我们要注意,我们不能直接 free(obj) ,因为obj 中只有最多两个节点,也就是头节点和尾节点
在这里插入图片描述
所以我们要循环遍历销毁,保证队列中每一个节点都被销毁。

代码整体

下面是全部代码:

//链表的结构体
typedef int CQDateType;
typedef struct ListNod {
    struct ListNod* next;
    CQDateType val;
}ListNod;

//循环队列的结构体
typedef struct {
    struct ListNod* phead;//存储头节点
    struct ListNod* ptail;//存储尾节点的下一个节点
} MyCircularQueue;

//开辟链表
ListNod* LTBuyNode()
{
    ListNod* plist = (ListNod*)malloc(sizeof(ListNod));
    if (plist == NULL)
    {
        perror("malloc fail");
        exit(1);
    }
    plist->next = NULL;
    plist->val = 0;
    return plist;
}

//函数声明
bool myCircularQueueIsEmpty(MyCircularQueue* obj);
bool myCircularQueueIsFull(MyCircularQueue* obj);

//开辟循环队列
MyCircularQueue* myCircularQueueCreate(int k) {
    //开辟循环队列
    MyCircularQueue* cq = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    if (cq == NULL)
    {
        perror("malloc fail");
        exit(1);
    }
    //先开辟一个节点数为 k + 1 的循环链表
    ListNod* head = LTBuyNode();
    ListNod* pcur = head;
    for (int i = 0; i < k; i++)
    {
        ListNod* tmp = LTBuyNode();
        pcur->next = tmp;
        pcur = pcur->next;
    }
    //出循环则变成一个 k + 1 个节点的链表
    //接下来将他们链接起来
    pcur->next = head;
    //将循环链表赋给循环队列
    cq->phead = head;
    cq->ptail = head;
    return cq;
}

//插入数据
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
    assert(obj);
    //判断队列是否为满
    if (myCircularQueueIsFull(obj))
    {
        return false;
    }
    //插入数据
    obj->ptail->val = value;
    obj->ptail = obj->ptail->next;
    return true;
}

//删除数据
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
    assert(obj);
    if (myCircularQueueIsEmpty(obj))
    {
        return false;
    }
    //删除数据
    obj->phead = obj->phead->next;
    return true;
}

//返回队首数据
int myCircularQueueFront(MyCircularQueue* obj) {
    assert(obj);
    if (myCircularQueueIsEmpty(obj))
    {
        return -1;
    }
    return obj->phead->val;
}

//返回队尾数据
int myCircularQueueRear(MyCircularQueue* obj) {
    assert(obj);
    if (myCircularQueueIsEmpty(obj))
    {
        return -1;
    }
    //遍历队列找到队尾
    ListNod* pcur = obj->phead;
    while (pcur->next != obj->ptail)
    {
        pcur = pcur->next;
    }
    return pcur->val;
}

//判断队列是否为空
//为空返回 true 
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
    assert(obj);
    return obj->phead == obj->ptail;
}

//判断队列是否为满
//为满返回 ture
bool myCircularQueueIsFull(MyCircularQueue* obj) {
    assert(obj);
    return obj->ptail->next == obj->phead;
}

//队列销毁
void myCircularQueueFree(MyCircularQueue* obj) {
    assert(obj);
    ListNod* pcur = obj->phead;
    while (pcur != obj->ptail)
    {
        ListNod* plist = pcur;
        pcur = pcur->next;
        free(plist);
        plist = NULL;
    }
    free(obj);
    obj = NULL;
}

用数组设计循环队列

用数组来设计循环队列,是有优势的因为数组方便访问下标,而且数组的缓存利用率高,就是代码实现起来相对有些绕,但是代码总量会少一点,能少个四十行左右,下面来说一下思路。

关于判断队列的空和满和上面一样,都是采用多开一个数组的方式。

但是数组的我们找数据的方式是用下标,那就会出现一个问题,就是当我们插入数据时插入到最后一个时会超出范围
在这里插入图片描述

这时我们可以令 (ptail + 1) % (k + 1)

这样就可以巧妙的将 ptail 的值维持在 k + 1 以内,并且还不会影响前面的值。后面会很多使用这个方法
首先是结构体的创建
在这里插入图片描述

判断队列是否为空

在这里插入图片描述

判断队列是否为满

在这里插入图片描述
这里就用了刚刚的方法,因为ptail 的下一个如果超出了数组的边界,就会出错。

插入数据

在这里插入图片描述
在插入时先将数据插入 ptail 处,再将ptail 向后一位。

删除数据

在这里插入图片描述
将数据删除,直接将 phead 向后移到,在数组中就是将 phead++ ,但因为有可能下一个超出数组边界,所以要 (ptail + 1) % (k + 1) 。

返回队尾数据

在这里插入图片描述
返回队尾数据不用向链表那样遍历,我们可以直接改变下标来访问,这也是对于这个题,用数组的优势。
在这里插入图片描述
如图,我们想要取队尾数据,就是 ptail 的前一个,正常的情况下我们只要对 ptail - 1就行,但是当 ptail 为对一个元素时,也就是当 ptail 为 0 时减 1 ,就会使 ptail 变为 -1,这样的下标会报错。
在这里插入图片描述

返回队首数据

在这里插入图片描述

队列销毁

在这里插入图片描述
这里我们在销毁时,我们要先释放队列结构体中我们 malloc 的数组,然后再释放 obj 。

代码整体

typedef int QDateType;
typedef struct {
    QDateType* arr;//用数组创建循环队列
    int phead;//队列头的下标
    int ptail;//队列尾的下标
    int k;//队列大小
} MyCircularQueue;

//函数声明
bool myCircularQueueIsEmpty(MyCircularQueue* obj);
bool myCircularQueueIsFull(MyCircularQueue* obj);

//队列初始化
MyCircularQueue* myCircularQueueCreate(int k) {
    MyCircularQueue* cq = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    QDateType* tmp = (QDateType*)malloc(sizeof(QDateType) * (k + 1));
    if (tmp == NULL)
    {
        perror("malloc fail:");
        exit(1);
    }
    cq->arr = tmp;
    cq->phead = cq->ptail = 0;
    cq->k = k;
    return cq;
}

//向队列插入数据
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
    assert(obj);
    //先判断队列是否满
    if (myCircularQueueIsFull(obj))
    {
        return false;
    }
    //向队列中插入数据
    obj->arr[obj->ptail] = value;
    obj->ptail = (obj->ptail + 1) % (obj->k + 1);
    return true;
}

//删除队列元素
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
    assert(obj);
    //先判断是否为空
    if (myCircularQueueIsEmpty(obj))
    {
        return false;
    }
    //删除数据
    obj->phead = (obj->phead + 1) % (obj->k + 1);
    return true;
}

//返回队头数据
int myCircularQueueFront(MyCircularQueue* obj) {
    assert(obj);
    //先判断是否为空
    if (myCircularQueueIsEmpty(obj))
    {
        return -1;
    }
    //返回队首数据
    return obj->arr[obj->phead];
}

//返回队尾数据
int myCircularQueueRear(MyCircularQueue* obj) {
    assert(obj);
    //先判断是否为空
    if (myCircularQueueIsEmpty(obj))
    {
        return -1;
    }
    //返回队尾元素
    return obj->arr[(obj->ptail + obj->k) % (obj->k + 1)];
    //           obj->ptail - 1 + obj->k + 1
}

//判断队列是否为空
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
    assert(obj);
    return obj->phead == obj->ptail;
}

//判断队列是否满
bool myCircularQueueIsFull(MyCircularQueue* obj) {
    assert(obj);
    //当ptail下一个是phead时为满
    return (obj->ptail + 1) % (obj->k + 1) == obj->phead;
}

//队列销毁
void myCircularQueueFree(MyCircularQueue* obj) {
    assert(obj);
    //先释放结构体中的元素
    free(obj->arr);
    obj->phead = obj->ptail = obj->k = 0;
    //最后释放结构体
    free(obj);
}

三、总结

对于这个题,我们采用了两种方法
用链表创建
用数组创建
这两种方法各有优势,用链表创建是在创建时会很麻烦要手动开辟一个循环链表,而数组就简单的多,但是当建立好链表后,后续的思路比较清晰没有那么多坑。
用数组创建的优势是可以下标访问,不用像链表那样还要循环遍历,降低了空间复杂度,而且数组在缓存利用率上有天然的优势。


http://www.kler.cn/news/357335.html

相关文章:

  • OJ-两个字符串间的最短路径问题
  • 在数据库中,`SELECT`, `FROM`, `JOIN`, `ON`, 和 `WHERE`各自的作用
  • csp普及组算法集训--Dfs
  • 一级注册消防工程师《消防安全技术实务》模拟试题及详解
  • 详解mac系统通过brew安装mongodb与使用
  • SpringCloud学习:Spring Cloud Alibaba Nacos(服务注册中心、配置管理中心)
  • PyTorch 实现自然语言分类
  • [PHP]Undefined index错误只针对数组
  • 如何有效保障专线健康:运维团队的专线监控策略
  • yjs机器学习数据操作01——数据的获取、可视化
  • 基于Flink MySQL CDC技术实现交易告警
  • 三、数据聚合和函数
  • 个人主页模版(源代码开源)
  • 界面控件Telerik UI for WPF 2024 Q3亮点 - 支持禁用数据过滤等
  • 蒙特卡洛算法:从概率模拟到机器学习模型优化的全方位解析
  • 第五届机器学习与计算机应用国际学术会议(ICMLCA 2024)
  • 同三维T80001JEHVA H.265 HDMI/VGA/CVBS解码器
  • c#编写的各类应用程序、类库的引用(黑白盒)
  • 软考(网工)——网络安全
  • YOLOv8实战人脸-口罩检测与识别【数据集+YOLOv8模型+源码+PyQt5界面】