【数据结构与算法】详解循环队列:基于数组实现高效存储与访问

简介: 【数据结构与算法】详解循环队列:基于数组实现高效存储与访问

一、引言

🍃队列的概念

队列(Queue)是一种常见的数据结构,它遵循先进先出(FIFO)的原则,即最早进入队列的元素将最先被移除。队列在计算机科学中有广泛的应用,比如任务调度、网络流量控制、打印任务管理等。然而,当我们在处理固定大小的空间时,传统的队列实现可能会遇到空间浪费的问题。为了解决这个问题,我们引入了循环队列(Circular Queue)的概念。

关于队列的详细介绍,请参考前置文章

【数据结构与算法】使用单链表实现队列:原理、步骤与应用-CSDN博客

🍃循环队列的概念

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

循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。相比于传统的队列实现,循环队列能够更有效地利用存储空间,并在数组大小固定的情况下实现队列的无限循环。在本文中,我们将详细探讨如何使用数组来实现循环队列,并分析其优势和应用场景。

🍃为什么使用数组实现循环队列

循环队列的实现方式主要是基于数组的,但也可以采用其他数据结构,如链表。不过,在实际应用中,数组实现循环队列的方式更为常见和高效。

基于数组实现循环队列的特点和优势

  1. 空间利用率高:通过将数组的最后一个位置与第一个位置相连,循环队列能够充分利用数组的存储空间,避免传统队列在多次入队和出队操作后可能出现的空间浪费现象。
  2. 操作简便:在数组实现中,可以通过简单的数学运算(如取模运算)来更新头指针和尾指针,实现入队和出队操作。这使得循环队列的操作非常简便和高效。
  3. 时间复杂度低:无论是入队还是出队操作,循环队列的时间复杂度都是O(1),即常数时间复杂度。这意味着无论队列中有多少元素,入队和出队操作所需的时间都是固定的。

循环队列在逻辑上的结构是这样的

但在物理上的结构是这样的

二、循环队列的结构定义

包含

  • 指向数组的指针,这是循环队列的底层结构
  • 指向队首和队尾的整型变量front和rear
  • 循环队列的空间大小k

typedef int CQueueDataType;
typedef struct MyCircularQueue//循环队列结构定义
{
  CQueueDataType* a;
  int front;
  int rear;
  int k;
} MyCircularQueue;

三、循环队列的接口实现

🍃队列初始化

  • 动态开辟一块循环队列结构体大小的空间
  • 为数组指针的指向地址分配一块动态申请的内存,大小为k+1个空间,但实际使用k个(不申请k个是为了区别队列空和队列满,保留一个空间)
  • front和rear初始为0(要注意rear初始为0,意味着指向的是队尾的下一个元素)
  • k初始化为输入的值
  • 最后返回该队列的地址
MyCircularQueue* myCircularQueueCreate(int k) //循环队列初始化
{
  MyCircularQueue* tmp = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
  tmp->a = (CQueueDataType*)malloc(sizeof(CQueueDataType) * (k + 1));
  tmp->front = tmp->rear = 0;
  tmp->k = k;
  return tmp;
}

🍃入队列

  • 首先对形参接收的地址判空
  • 然后判断队列是否满
  • 如果有空间可用的话,在rear指向的位置插入数据
  • 调整rear的位置,向后移动注意考虑循环的问题(rear+1)%(k+1),先对rear+1再对数组长度取模

bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) //入队列
{
  assert(obj);
  if (myCircularQueueIsFull(obj))
    return false;
  obj->a[obj->rear] = value;
  obj->rear = (obj->rear + 1) % (obj->k + 1);
  return true;
}

🍃出队列

  • 首先对形参接收的地址判空
  • 然后判断队列是否为空
  • 如果有数据可出的话,直接调整front的位置即可(不过应当考虑循环值溢出的问题)(front+1)%(k+1)
  • 先对front+1再对数组长度取模

bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) //入队列
{
  assert(obj);
  if (myCircularQueueIsFull(obj))
    return false;
  obj->a[obj->rear] = value;
  obj->rear = (obj->rear + 1) % (obj->k + 1);
  return true;
}

🍃取队首元素

  • 首先对形参接收的地址判空
  • 然后判断队列是否为空(空队列无数据可取)
  • 然后返回front位置的元素即可

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

🍃取队尾元素

  • 首先对形参接收的地址判空
  • 然后判断队列是否为空(空队列无数据可取)
  • 队尾元素是rear位置的前一个元素,考虑到直接-1可能会出错,正确的位置应该是(rear - 1 + k + 1) % (k + 1),也可以简化成(rear  +k ) % (k + 1)
  • 返回该位置数据即可

int myCircularQueueFront(MyCircularQueue* obj) //取队首元素
{
  assert(obj);
  if (myCircularQueueIsEmpty(obj))
    return -1;
  return obj->a[obj->front];
}

🍃判空

  • 对形参接收的地址判空
  • 然后返回front==rear的结果
bool myCircularQueueIsEmpty(MyCircularQueue* obj) //判空
{
  assert(obj);
  return obj->front == obj->rear;
}

🍃判满

  • 对形参接收的地址判空
  • 队列满的条件理应是rear+1==front,但考虑到队列是一个"环形"的,要考虑值的溢出,所以改为(rear + 1 )% (k +1)==front
bool myCircularQueueIsFull(MyCircularQueue* obj) //判满
{
  assert(obj);
  return (obj->rear + 1) % (obj->k + 1)==(obj->front);
}

🍃队列销毁

  • 对形参接收的地址判空
  • 然后释放动态申请的数组的空间
  • front、rear、k都重置为0
  • 最后释放循环队列结构体的空间
void myCircularQueueFree(MyCircularQueue* obj) //循环队列销毁
{
  free(obj->a);
  obj->front = obj->rear = 0;
  obj->k = 0;
  free(obj);
  obj = NULL;
}

四、C语言实现代码

🍃Circular_Queue.h   //循环队列头文件

#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
 
typedef int CQueueDataType;
typedef struct MyCircularQueue//循环队列结构定义
{
  CQueueDataType* a;
  int front;
  int rear;
  int k;
} MyCircularQueue;
 
 
MyCircularQueue* myCircularQueueCreate(int k); //循环队列初始化
 
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value);//入队列
 
bool myCircularQueueDeQueue(MyCircularQueue* obj);//出队列
 
int myCircularQueueFront(MyCircularQueue* obj);//取队首元素
 
int myCircularQueueRear(MyCircularQueue* obj); //取队尾元素
 
bool myCircularQueueIsEmpty(MyCircularQueue* obj); //判空
 
bool myCircularQueueIsFull(MyCircularQueue* obj);//判满
 
void myCircularQueueFree(MyCircularQueue* obj); //循环队列销毁

🍃Circular_Queue.c   //循环队列源文件

#include"Circular_Queue.h"
 
MyCircularQueue* myCircularQueueCreate(int k) //循环队列初始化
{
  MyCircularQueue* tmp = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
  tmp->a = (CQueueDataType*)malloc(sizeof(CQueueDataType) * (k + 1));
  tmp->front = tmp->rear = 0;
  tmp->k = k;
  return tmp;
}
 
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) //入队列
{
  assert(obj);
  if (myCircularQueueIsFull(obj))
    return false;
  obj->a[obj->rear] = value;
  obj->rear = (obj->rear + 1) % (obj->k + 1);
  return true;
}
 
bool myCircularQueueDeQueue(MyCircularQueue* obj) //出队列
{
  assert(obj);
  if (myCircularQueueIsEmpty(obj))
    return false;
  obj->front = (obj->front + 1) % (obj->k + 1);
  return true;
}
 
int myCircularQueueFront(MyCircularQueue* obj) //取队首元素
{
  assert(obj);
  if (myCircularQueueIsEmpty(obj))
    return -1;
  return obj->a[obj->front];
}
 
int myCircularQueueRear(MyCircularQueue* obj) //取队尾元素
{
  assert(obj);
  if (myCircularQueueIsEmpty(obj))
    return -1;
  return obj->a[(obj->rear - 1 + obj->k + 1) % (obj->k + 1)];
}
 
bool myCircularQueueIsEmpty(MyCircularQueue* obj) //判空
{
  assert(obj);
  return obj->front == obj->rear;
}
 
bool myCircularQueueIsFull(MyCircularQueue* obj) //判满
{
  assert(obj);
  return (obj->rear + 1) % (obj->k + 1)==(obj->front);
}
 
void myCircularQueueFree(MyCircularQueue* obj) //循环队列销毁
{
  free(obj->a);
  obj->front = obj->rear = 0;
  obj->k = 0;
  free(obj);
  obj = NULL;
}

🍃test.c           //main函数测试文件

#include"Circular_Queue.h"
 
void test1()
{
  int k = 0;
  scanf("%d", &k);
  MyCircularQueue* CQ = myCircularQueueCreate(k);//创建循环队列并初始化
  if (myCircularQueueIsEmpty(CQ))
    printf("队列空\n");
  myCircularQueueEnQueue(CQ, 1);//插入五个数据
  myCircularQueueEnQueue(CQ, 2);
  myCircularQueueEnQueue(CQ, 3);
  myCircularQueueEnQueue(CQ, 4);
  myCircularQueueEnQueue(CQ, 5);
  if (myCircularQueueIsEmpty(CQ))
    printf("队列空\n");
  else
    printf("队列非空\n");
  if (myCircularQueueIsFull(CQ))
    printf("队列满\n");
  else
    printf("队列非满\n");
  printf("队首元素:%d\n", myCircularQueueFront(CQ));
  printf("队尾元素:%d\n", myCircularQueueRear(CQ));
  while (!myCircularQueueIsEmpty(CQ))//依次打印队首元素并删除
  {
    printf("%d ", myCircularQueueFront(CQ));
    myCircularQueueDeQueue(CQ);
  }
  printf("\n");
  myCircularQueueFree(CQ);
}
 
int main()
{
  test1();
  return 0;
}

测试结果

五、循环队列的应用场景

循环队列在实际应用中有着广泛的用途。以下是一些常见的应用场景:

  1. 任务调度:在操作系统中,任务调度器通常使用队列来管理待执行的任务。循环队列可以有效地处理这些任务,确保它们按照先进先出的顺序被执行。由于操作系统的资源有限,使用循环队列可以最大化地利用这些资源,避免不必要的空间浪费。
  2. 网络通信:在网络通信中,数据包经常需要在不同的节点之间传输。循环队列可以用于在节点上管理这些数据包,确保它们按照正确的顺序被发送和接收。特别是在路由器和交换机等网络设备中,循环队列可以有效地处理大量的数据包,提高网络性能。
  3. 打印机管理:在打印系统中,多个打印任务可能需要同时发送到打印机。循环队列可以用于管理这些打印任务,确保它们按照接收的顺序被打印出来。使用循环队列可以避免打印任务的混乱和丢失,提高打印效率。
  4. 模拟系统:在模拟系统中,如模拟银行排队系统或模拟医院挂号系统等,循环队列可以模拟现实中的排队情况。通过循环队列的入队和出队操作,可以模拟客户的到来和离开,以及服务员的接待过程。这有助于分析系统的性能和瓶颈,优化服务流程。

六、总结

循环队列是一种利用数组循环特性实现队列操作的数据结构。它通过维护头指针和尾指针来管理队列的入队和出队操作,实现了对固定大小空间的高效利用。

循环队列在任务调度、网络通信、打印机管理以及模拟系统等多个领域都有广泛的应用。

通过本文的介绍和分析,我们可以看到循环队列在解决实际问题时具有显著的优势和灵活性。因此,掌握循环队列的实现原理和应用方法对于提高编程能力和解决实际问题具有重要意义。

目录
打赏
0
1
1
0
21
分享
相关文章
基于 C++ 布隆过滤器算法的局域网上网行为控制:URL 访问过滤的高效实现研究
本文探讨了一种基于布隆过滤器的局域网上网行为控制方法,旨在解决传统黑白名单机制在处理海量URL数据时存储与查询效率低的问题。通过C++实现URL访问过滤功能,实验表明该方法可将内存占用降至传统方案的八分之一,查询速度提升约40%,假阳性率可控。研究为优化企业网络管理提供了新思路,并提出结合机器学习、改进哈希函数及分布式协同等未来优化方向。
35 0
基于game-based算法的动态频谱访问matlab仿真
本算法展示了在认知无线电网络中,通过游戏理论优化动态频谱访问,提高频谱利用率和物理层安全性。程序运行效果包括负载因子、传输功率、信噪比对用户效用和保密率的影响分析。软件版本:Matlab 2022a。完整代码包含详细中文注释和操作视频。
关于员工上网监控系统中 PHP 关联数组算法的学术解析
在当代企业管理中,员工上网监控系统是维护信息安全和提升工作效率的关键工具。PHP 中的关联数组凭借其灵活的键值对存储方式,在记录员工网络活动、管理访问规则及分析上网行为等方面发挥重要作用。通过关联数组,系统能高效记录每位员工的上网历史,设定网站访问权限,并统计不同类型的网站访问频率,帮助企业洞察员工上网模式,发现潜在问题并采取相应管理措施,从而保障信息安全和提高工作效率。
59 7
C 408—《数据结构》算法题基础篇—数组(通俗易懂)
408考研——《数据结构》算法题基础篇之数组。(408算法题的入门)
179 23
|
9月前
|
Leetcode 初级算法 --- 数组篇
Leetcode 初级算法 --- 数组篇
92 0
C语言在实现高效算法方面的特点与优势,包括高效性、灵活性、可移植性和底层访问能力
本文探讨了C语言在实现高效算法方面的特点与优势,包括高效性、灵活性、可移植性和底层访问能力。文章还分析了数据结构的选择与优化、算法设计的优化策略、内存管理和代码优化技巧,并通过实际案例展示了C语言在排序和图遍历算法中的高效实现。
208 2
数据结构与算法学习七:栈、数组模拟栈、单链表模拟栈、栈应用实例 实现 综合计算器
栈的基本概念、应用场景以及如何使用数组和单链表模拟栈,并展示了如何利用栈和中缀表达式实现一个综合计算器。
140 1
数据结构与算法学习七:栈、数组模拟栈、单链表模拟栈、栈应用实例 实现 综合计算器
数据结构与算法学习二、稀疏数组与队列,数组模拟队列,模拟环形队列
这篇文章主要介绍了稀疏数组和队列的概念、应用实例以及如何使用数组模拟队列和环形队列的实现方法。
101 0
数据结构与算法学习二、稀疏数组与队列,数组模拟队列,模拟环形队列
|
9月前
|
【初阶数据结构】深入解析循环队列:探索底层逻辑
【初阶数据结构】深入解析循环队列:探索底层逻辑
173 0
深入算法基础二分查找数组
文章深入学习了二分查找算法的基础,通过实战例子详细解释了算法的逻辑流程,强调了确定合法搜索边界的重要性,并提供了Java语言的代码实现。
深入算法基础二分查找数组

热门文章

最新文章

AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等

登录插画

登录以查看您的控制台资源

管理云资源
状态一览
快捷访问