【数据结构和算法】--队列的特殊结构-循环队列

简介: 【数据结构和算法】--队列的特殊结构-循环队列

循环队列的结构

循环队列是队列的一种特殊结构,它的长度是固定的k,同样是先进先出,理论结构是首尾相连的环形循环结构。其理论结构大致如下:

具体结构描述可以参考LeetCode: 622. 设计循环队列的题目要求,大致如下:

设计你的循环队列实现。 循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。

循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。

循环队列的实现

循环队列的实现方式同样有两种–数组,链表

  1. 数组循环队列:数组实现方式顾名思义就是动态开辟一个长度为k的数组,那要怎么达到循环呢?我们可以定义两个数一个指向队列头元素(int front),一个指向队列尾元素的下一个(int back),(此处指向队尾下一个是为了方便队列空和满的判断),这样当back走到最后一个时,我们只需要将他重新置成0便又到了队列第一个元素,如此设计理论上就达到了循环结构。

      新的问题又来了:当front == back时要怎么区分队列是空还是满?两种解决方案:

  1. 增加一个size记录有效数据的节点数size == 0队列就是空,size == k队列就是满。
  2. 多开辟一个空间k+1front == back便是空,(back+1)%(k+1) == front就是满(即在理论结构中back指向的下一个位置是front,下文会讲解)。

链表循环队列:

链表实现的循环队列更有点循环的味(即将尾指针的next指向头,形成真正的循环),但实现起来不如数组方便。链表实现循环队列同样要定义两个指针,一个指向循环队列的头元素(phead),一个指向循环队列尾元素的下一个(ptail)。判断循环队列空和满的方法和数组相似,只不过判断条件从判断值相同改为判断址相同,第二种方法判满改为phead == ptail->next。

但用链表设计循环队列也会有新的困难:1. 获取循环队列尾元素不方便,还要遍历队列寻找;2. 定义结构体时,还要多定义一个装链表节点的结构体,这也增加了代码的难度。


所以不论是用数组还是用链表实现循环队列,都有各自的好处和问题,下面实现循环队列我所介绍的方法是数组实现法判满和判空用的是多定义一个节点法

依据上述方法,可以定义如下结构体变量:

typedef struct
{
    int* a;//数组
    int front;//队列头
    int back;//队列尾下一个位置
    int k;//循环队列可存储数据长度
} MyCircularQueue;

循环队列的创建

注意此处所给的函数返回值类型MyCircularQueue,正是上述结构体类型。故须先动态开辟一个结构体类型大小,并将frontback初始化为0,然后再利用结构体指针来开辟长度为k+1的数组最后返回结构体指针

这样动态开辟而不直接定义结构体变量(MyCircularQueue ps),是因为这是在函数中,出了函数的作用域此结构体变量就会销毁,所以需要malloc将结构体动态开辟在堆区。

MyCircularQueue* myCircularQueueCreate(int k)
{
    MyCircularQueue* ps = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    ps->a = (int*)malloc(sizeof(int)*(k+1));
    ps->back = ps->front = 0;
    ps->k = k;
    return ps;
}

循环队列为空判断

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

循环队列为满判断

循环队列为满大致可以分为以上两种情况。

  1. 情况1:
    当队列尾back来到最后一个时,此时如果back + 1的话就会超过k + 1的范围,而我们的目的是想知道在循环队列中back + 1后的位置(即下标为0的位置),所以此时我们只要将(obj->back + 1)%(obj->k + 1),此式的值便为0
  2. 情况2:
    back + 1的范围在k + 1内,此时判断back + 1即可,若(obj->back + 1)%(obj->k + 1)也不会影响值。

如此将两式合并,便得到了简化的效果。

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

入队

题目描述:enQueue(value):向循环队列插入一个元素。如果成功插入则返回真。

既如此那么先判断循环队列是否已满,若满则返回false,否则入队新值,并将obj->back值更新(obj->back %= obj->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;
    return true;
}

出队

题目描述:deQueue():从循环队列中删除一个元素。如果成功删除则返回真。

与入循环队列相似。

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

返回循环队列首元素

题目描述:Front:从队首获取元素。如果队列为空,返回 -1 。

先判断循环队列不为空,若为空返回-1,不为空返回下标为obj->front的值。

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

返回循环队列尾元素

题目描述:Rear:获取队尾元素。如果队列为空,返回 -1 。

同样要先判断循环队列是否为空,为空返回-1。此处计算obj->back上一个的下标的方法中,obj->back-1后还要加obj->k+1是为了防止obj->back指向下标为0的情况,再% (obj->k+1)是为了防止超出下标范围的情况,与判断队满相似。

int myCircularQueueRear(MyCircularQueue* obj)
{
    if(myCircularQueueIsEmpty(obj))
        return -1;
    return obj->a[(obj->back-1+obj->k+1) % (obj->k+1)];
}

释放循环队列

依次释放即可,先释放最内层的数组,然后释放最外层的结构体。

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


目录
相关文章
|
11天前
|
存储 Java
【数据结构】优先级队列(堆)从实现到应用详解
本文介绍了优先级队列的概念及其底层数据结构——堆。优先级队列根据元素的优先级而非插入顺序进行出队操作。JDK1.8中的`PriorityQueue`使用堆实现,堆分为大根堆和小根堆。大根堆中每个节点的值都不小于其子节点的值,小根堆则相反。文章详细讲解了如何通过数组模拟实现堆,并提供了创建、插入、删除以及获取堆顶元素的具体步骤。此外,还介绍了堆排序及解决Top K问题的应用,并展示了Java中`PriorityQueue`的基本用法和注意事项。
21 5
【数据结构】优先级队列(堆)从实现到应用详解
|
2天前
|
存储
|
19天前
|
存储 算法 C语言
数据结构基础详解(C语言): 二叉树的遍历_线索二叉树_树的存储结构_树与森林详解
本文从二叉树遍历入手,详细介绍了先序、中序和后序遍历方法,并探讨了如何构建二叉树及线索二叉树的概念。接着,文章讲解了树和森林的存储结构,特别是如何将树与森林转换为二叉树形式,以便利用二叉树的遍历方法。最后,讨论了树和森林的遍历算法,包括先根、后根和层次遍历。通过这些内容,读者可以全面了解二叉树及其相关概念。
|
19天前
|
存储 机器学习/深度学习 C语言
数据结构基础详解(C语言): 树与二叉树的基本类型与存储结构详解
本文介绍了树和二叉树的基本概念及性质。树是由节点组成的层次结构,其中节点的度为其分支数量,树的度为树中最大节点度数。二叉树是一种特殊的树,其节点最多有两个子节点,具有多种性质,如叶子节点数与度为2的节点数之间的关系。此外,还介绍了二叉树的不同形态,包括满二叉树、完全二叉树、二叉排序树和平衡二叉树,并探讨了二叉树的顺序存储和链式存储结构。
|
19天前
|
存储 C语言
数据结构基础详解(C语言): 栈与队列的详解附完整代码
栈是一种仅允许在一端进行插入和删除操作的线性表,常用于解决括号匹配、函数调用等问题。栈分为顺序栈和链栈,顺序栈使用数组存储,链栈基于单链表实现。栈的主要操作包括初始化、销毁、入栈、出栈等。栈的应用广泛,如表达式求值、递归等场景。栈的顺序存储结构由数组和栈顶指针构成,链栈则基于单链表的头插法实现。
123 3
|
20天前
|
Java
【数据结构】栈和队列的深度探索,从实现到应用详解
本文介绍了栈和队列这两种数据结构。栈是一种后进先出(LIFO)的数据结构,元素只能从栈顶进行插入和删除。栈的基本操作包括压栈、出栈、获取栈顶元素、判断是否为空及获取栈的大小。栈可以通过数组或链表实现,并可用于将递归转化为循环。队列则是一种先进先出(FIFO)的数据结构,元素只能从队尾插入,从队首移除。队列的基本操作包括入队、出队、获取队首元素、判断是否为空及获取队列大小。队列可通过双向链表或数组实现。此外,双端队列(Deque)支持两端插入和删除元素,提供了更丰富的操作。
23 0
【数据结构】栈和队列的深度探索,从实现到应用详解
|
1月前
|
机器学习/深度学习 消息中间件 缓存
栈与队列的实现
栈与队列的实现
39 0
|
1月前
|
算法
【初阶数据结构篇】二叉树算法题
二叉树是否对称,即左右子树是否对称.
|
17天前
|
存储 人工智能 C语言
数据结构基础详解(C语言): 栈的括号匹配(实战)与栈的表达式求值&&特殊矩阵的压缩存储
本文首先介绍了栈的应用之一——括号匹配,利用栈的特性实现左右括号的匹配检测。接着详细描述了南京理工大学的一道编程题,要求判断输入字符串中的括号是否正确匹配,并给出了完整的代码示例。此外,还探讨了栈在表达式求值中的应用,包括中缀、后缀和前缀表达式的转换与计算方法。最后,文章介绍了矩阵的压缩存储技术,涵盖对称矩阵、三角矩阵及稀疏矩阵的不同压缩存储策略,提高存储效率。
|
1月前
栈的几个经典应用,真的绝了
文章总结了栈的几个经典应用场景,包括使用两个栈来实现队列的功能以及利用栈进行对称匹配,并通过LeetCode上的题目示例展示了栈在实际问题中的应用。
栈的几个经典应用,真的绝了