【数据结构】循环队列(数组实现)

目录

一、循环队列定义

怎么使一个数组在逻辑上呈“环状”呢?

 二、循环队列与顺序队列的差异

1、存储方式:

2、操作方式:

3、空间利用率:

4、循环队列判断队空的方式:

5、循环队列判断队满的方式

完整测试代码及注释: 

总结:


一、循环队列定义

将顺序存储队列的元素的一维数组首尾相接,形成一个环状,如下图所示,这种形式表示的队列称为循环队列。循环队列仍然是顺序队列结构,只是逻辑上和前面的顺序队列有所不同。

#define MAXLEN 6           // 定义环形队列的最大长度为 6typedef int DataType;     // 定义数据类型为整型typedef struct CircularQueue  // 定义环形队列的结构体
{DataType a[MAXLEN];   // 定义存储数据的数组int front, rear;      // 定义队头和队尾指针int size;             // 定义队列元素个数
} CQueue;void InitCQueue(CQueue* q)  // 初始化环形队列
{q->front = q->rear = 0; // 队头和队尾指针都指向队列的开始位置q->size = 0;            // 队列元素个数为 0,即初始为空队列
}

怎么使一个数组在逻辑上呈“环状”呢?

在数据结构中,可以使用一个 front 指针和一个 rear 指针来表示环状队列的队头和队尾位置,当 rear 指针移动到数组的最后一个位置时,如果再有元素需要入队,那么应该将 rear 指针指向数组的第一个位置。同样地,当 front 指针移动到数组的最后一个位置时,如果还有元素需要出队,那么应该将 front 指针指向数组的第一个位置。

具体实现方法如下:

  1. 初始化:定义一个数组和两个指针 front 和 rear,初始化时,将 front 指针和 rear 指针都指向数组的第一个位置。

  2. 入队:如果队列未满,则将元素插入 rear 指向的位置,然后将 rear 指针后移一位。当 rear 指针移动到数组的最后一个位置时,若队列未满,则将 rear 指针指向数组的第一个位置。

  3. 出队:如果队列非空,则将队头元素取出,然后将 front 指针后移一位。当 front 指针移动到数组的最后一个位置时,只要队列非空,就将 front 指针指向数组的第一个位置。

假设队列开辟的数组单元数为MAXSIZE,它的数组下标在0~MAXSIZE-1之间,若使队头或队尾增1,且使front和rear指针对应的数组下标保持在数组范围内,可以利用取模运算实现。


例如,在下图所示的循环队列示意图最大空间为MAXSIZE=8,数组下标为0~7之间。

非空队时如图(2)中队头指针front指向队列中队头元素的前一个位置队尾指针rear 指向队列的队尾元素位置。

  •         入队时的队尾指针加1操作修改为: rear=(rear+1)%MAXSIZE;
  •         出队时的队头指针加1操作修改为:front=(front+1)%MAXSIZE;

入队代码实现: 

void CQueuePush(CQueue* q, DataType x)   // 元素入队
{assert(q);  // 判断 q 是否为空if (!CQueueFull(q))  // 如果队列未满{q->rear = (q->rear + 1) % MAXLEN;   // 队尾指针后移一位q->a[q->rear] = x;  // 在队尾处添加元素q->size++;  // 队列元素个数加 1}else    // 队列已满,无法添加数据{printf("队列已满,无法添加数据!\n");exit(-1);}
}

 出队代码实现: 

void CQueuePop(CQueue* q)   // 元素出队
{assert(q);  // 判断 q 是否为空if (!CQueueEmpty(q))    // 如果队列非空{q->front = (q->front + 1) % MAXLEN; // 队头指针后移一位q->size--;  // 队列元素个数减 1}else    // 队列已空,无法删除数据{printf("队列已空,无法删除数据!\n");exit(-1);}
}


 二、循环队列与顺序队列的差异

  • 1、存储方式:

    • 顺序队列:使用数组作为底层数据结构,按照顺序存储元素。
    • 循环队列:仍然使用数组作为底层数据结构,但是通过循环利用数组的空间,实现循环存储。
  • 2、操作方式:

    • 顺序队列:使用两个指针front和rear分别表示队头和队尾,元素入队时rear指针后移,元素出队时front指针后移。
    • 循环队列:同样使用两个指针front和rear表示队头和队尾,但是在数据满或空的状态下,指针继续向后移动的时候保持循环关系。
      • 入队时队尾指针+1:rear=(rear+1)%MAXSIZE;
      • 出队时队头指针+1:front=(front+1)%MAXSIZE;
  • 3、空间利用率:

    • 顺序队列:存储元素的空间是连续的,当队列未满但是数组的末尾已经被利用时,无法继续插入元素。
    • 循环队列:通过循环利用数组空间,解决了顺序队列存储空间的浪费问题,可以实现更高的空间利用率。

4、循环队列判断队空的方式:

图(1)中队头与队尾指针指向同一位置时为空队,判断方法与顺序队列一致。

 代码实现:

int CQueueEmpty(CQueue* q)  // 判断队列是否为空
{assert(q);  // 判断 q 是否为空if (q->front == q->rear)  // 通过队头和队尾指针是否相等,判断队列是否为空{return 1;   // 队列为空}return 0;       // 队列非空
}

5、循环队列判断队满的方式

由 图(3) 可见,循环队列解决了顺序队列中“假溢出”的现象,充分利用了固定长度的队列中的空间。我们知道,在长度不可增长的顺序队列中,判断队列是否队满的条件是rear==MAXLEN。那么在循环队列中,我们判断队满的方式则为:(rear+1)%MAXLEN==front;  


 

代码实现:

int CQueueFull(CQueue* q)   // 判断队列是否为满
{assert(q);  // 判断 q 是否为空if ((q->rear + 1) % MAXLEN == q->front)  // 通过队尾和队头指针是否相邻,判断队列是否为满{return 1;   // 队列为满}return 0;       // 队列未满
}

我们理解完顺序队列与循环队列的差异后,在固定长度代码的基础上对front、rear指针的移动判满操作进行修改即可得到循环队列的代码。


完整测试代码及注释: 

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>#define MAXLEN 6           // 定义环形队列的最大长度为 6typedef int DataType;     // 定义数据类型为整型typedef struct CircularQueue  // 定义环形队列的结构体
{DataType a[MAXLEN];   // 定义存储数据的数组int front, rear;      // 定义队头和队尾指针int size;             // 定义队列元素个数
} CQueue;void InitCQueue(CQueue* q)  // 初始化环形队列
{q->front = q->rear = 0; // 队头和队尾指针都指向队列的开始位置q->size = 0;            // 队列元素个数为 0,即初始为空队列
}int CQueueFull(CQueue* q)   // 判断队列是否为满
{assert(q);  // 判断 q 是否为空if ((q->rear + 1) % MAXLEN == q->front)  // 通过队尾和队头指针是否相邻,判断队列是否为满{return 1;   // 队列为满}return 0;       // 队列未满
}int CQueueEmpty(CQueue* q)  // 判断队列是否为空
{assert(q);  // 判断 q 是否为空if (q->front == q->rear)  // 通过队头和队尾指针是否相等,判断队列是否为空{return 1;   // 队列为空}return 0;       // 队列非空
}void CQueuePush(CQueue* q, DataType x)   // 元素入队
{assert(q);  // 判断 q 是否为空if (!CQueueFull(q))  // 如果队列未满{q->rear = (q->rear + 1) % MAXLEN;   // 队尾指针后移一位q->a[q->rear] = x;  // 在队尾处添加元素q->size++;  // 队列元素个数加 1}else    // 队列已满,无法添加数据{printf("队列已满,无法添加数据!\n");exit(-1);}
}void CQueuePop(CQueue* q)   // 元素出队
{assert(q);  // 判断 q 是否为空if (!CQueueEmpty(q))    // 如果队列非空{q->front = (q->front + 1) % MAXLEN; // 队头指针后移一位q->size--;  // 队列元素个数减 1}else    // 队列已空,无法删除数据{printf("队列已空,无法删除数据!\n");exit(-1);}
}int CQueueTop(CQueue* q)   // 获取队首元素
{if (!CQueueEmpty(q))    // 如果队列非空{return q->a[q->front + 1];  // 返回队头下一个位置的元素}else    // 队列已空,无法获取队首数据{printf("队列已空,无法获取队首数据!\n");exit(-1);}
}int CQueueTail(CQueue* q)   // 获取队尾元素
{if (!CQueueEmpty(q))    // 如果队列非空{return q->a[q->rear];   // 返回队尾位置的元素}else    // 队列已空,无法获取队尾数据{printf("队列已空,无法获取队尾数据!\n");exit(-1);}
}int main()
{CQueue q;InitCQueue(&q);CQueuePush(&q, 1);CQueuePush(&q, 2);CQueuePush(&q, 3);CQueuePush(&q, 4);CQueuePush(&q, 5);CQueuePop(&q);CQueuePop(&q);CQueuePop(&q);CQueuePop(&q);//CQueuePop(&q);int x;x = CQueueTop(&q);printf("%d\n", x);x = CQueueTail(&q);printf("%d\n", x);return 0;
}

总结:

循环队列通过环形数组的设计,充分利用了存储空间,并实现了高效的元素入队和出队操作。在使用循环队列时,需要特别注意队列为空和队列满的判断,避免出现错误。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Vue2 - diff 原理(动图演示)

目录 1&#xff0c;diffdiff 的时间点 2&#xff0c;_update 函数3&#xff0c;_patch 函数&#xff08;进行 diff&#xff09;3.1&#xff0c;根节点比较3.2&#xff0c;子节点比较 4&#xff0c;key的问题举例1举例2 1&#xff0c;diff 解释&#xff1a;对比新旧虚拟DOM树&a…

scratch新跳7游戏 2023年12月中国电子学会图形化编程 少儿编程 scratch编程等级考试四级真题和答案解析

目录 scratch新跳7游戏 一、题目要求 1、准备工作 2、功能实现 二、案例分析

Unity | Shader基础知识番外(向量数学知识速成)

目录 一、向量定义 二、计算向量 三、向量的加法&#xff08;连续行走&#xff09; 四、向量的长度 五、单位向量 六、向量的点积 1 计算 2 作用 七、向量的叉乘 1 承上启下 2 叉乘结论 3 叉乘的计算&#xff08;这里看不懂就百度叉乘计算&#xff09; 八、欢迎收…

electron进程通信之预加载脚本和渲染进程对主进程通信

主进程和预加载脚本通信 主进程 mian,js 和预加载脚本preload.js,在主进程中创建预加载脚本, const createWindow () > {// Create the browser window.const mainWindow new BrowserWindow({width: 300,height: 300,// 指定预加载脚本webPreferences: {preload: path.j…

el-table表格动态添加列。多组数据拼接和多层级数据的处理

提示&#xff1a;el-table表格动态添加列 文章目录 前言一、多组数据拼接二、多层级处理三、实际应用中&#xff0c;为避免闪屏&#xff0c;可以表格数据统一渲染总结 前言 需求&#xff1a;富文本编辑器 一、多组数据拼接 <template><div class"test">…

鸿蒙HarmonyOs:为什么不支持热更新?

学习了一段时间的鸿蒙开发&#xff0c;发现鸿蒙开发还是比较简单的&#xff0c;今天突然心血来潮&#xff0c;研究了一下鸿蒙热更新&#xff0c;最终得出的结论是鸿蒙暂时不支持热更新。 鸿蒙app开发主要是利用的ArkTs语言&#xff0c;ArkTs又是基于TypeScript语言的&#xff0…

Linux mcd命令教程:如何在MS-DOS文件系统中切换工作目录(附实例教程和注意事项)

Linux mcd命令介绍 mcd是mtools工具的指令&#xff0c;它用于在MS-DOS文件系统中切换工作目录。如果不加任何参数&#xff0c;它将显示当前所在的磁盘和工作目录。 Linux mcd命令适用的Linux版本 mcd命令在所有主流的Linux发行版中都可以使用&#xff0c;包括但不限于Ubuntu…

八大算法排序@堆排序(C语言版本)

目录 堆排序大堆排序概念算法思想建堆建堆核心算法建堆的代码 排序代码实现 小堆排序代码实现时间复杂度空间复杂度 堆排序 堆排序借用的是堆的特性来实现排序功能的。大堆需要满足父节点大于子节点&#xff0c;因此堆顶是整个数组中的最大元素。小堆则相反&#xff0c;要求父节…

docker搭建Dinky —— 筑梦之路

简介 Dinky 是一个 开箱即用 、易扩展 &#xff0c;以 Apache Flink 为基础&#xff0c;连接 OLAP 和 数据湖 等众多框架的 一站式 实时计算平台&#xff0c;致力于 流批一体 和 湖仓一体 的探索与实践。 主要功能 其主要功能如下&#xff1a; 沉浸式 FlinkSQL 数据开发&#x…

极狐GitLab Helm Chart 已上线,玩转云原生极狐GitLab!

极狐GitLab 研发团队提供了极狐GitLab & Runner 的 Helm Chart&#xff0c;方便用户在 Kubernetes 相关环境上来安装和运行极狐GitLab & Runner。Helm Chart 已经上线 Artifact Hub &#xff1a; 使用指南 只需简单两步就可以开启极狐GitLab & Runner Helm Chart …

python flask图书管理系统带文档

python flask图书管理系统带文档。功能&#xff1a;登录&#xff0c;图书的增删改查&#xff0c;读者管理&#xff0c;借阅记录&#xff0c;有文档。 技术&#xff1a;python3,flask,mysql,html。 包含源码数据库文件文档。 源码下载地址&#xff1a; https://download.csd…

文章解读与仿真程序复现思路——电网技术EI\CSCD\北大核心《基于碳捕集与封存-电转气-电解熔融盐协同的虚拟电厂优化调度》

本专栏栏目提供文章与程序复现思路&#xff0c;具体已有的论文与论文源程序可翻阅本博主的专栏栏目《论文与完整程序》 这个标题涉及到多个关键概念&#xff0c;让我们逐一解读&#xff1a; 碳捕集与封存&#xff08;Carbon Capture and Storage&#xff0c;CCS&#xff09;&a…