循环队列原理及在单片机串口通讯的应用(一)
2021/6/9 10:52:09
本文主要是介绍循环队列原理及在单片机串口通讯的应用(一),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
前言
当代码,不再是简单的完成需求,对代码进行堆砌,而是开始思考如何写出优美代码的时候,我们的代码水平必然会不断提升,今天,咱们来学习环形队列结构。
环形队列的基本概念
相信对数据结构有过接触的小伙伴,对队列肯定不会陌生,队列相对来说是比较简单的数据结构,典型特点是FIFO,即First in First out,先进先出,就像我们日常排队买票一样,先到的人先买票,先从购票口出去,从下面的图中,可以比较形象的了解队列的特性。
用数组创建一个普通队列,当有数据存储时,队列尾指针不断增加,直到空间用完,当数据出队列时,队列头指针不断增加,直至和队列尾相同时,所有数据完成出队列,那么这时候会引入一个问题,全部出队后,将无法继续入队,这样的情况也叫做“假溢出”,即使数组中,明明还有空间可以利用,但是却无法使用(平时我们做串口接收的时候,往往是通过清零计数器,清空数组,重新接收来解决这一问题)。
只要思想不滑坡,方法总比困难多。为了解决上面“假溢出”的现象,环形队列应运而生,即通常将一维数组Queue[0]到Queue[MAXSIZE - 1]看成是一个首尾相连接的圆环,即Queue[0]与Queue[MAXSIZE - 1]相连接在一起,将这样形式的队列成为循环队列。
环形队列实现原理
在计算机的内存中,是不存在所谓的环形内存区域的,所以,需要程序员认为的“画个圈圈”,从图示环形队列来看,存储空间有限,当数据存到末端时,如何处理呢,只需要重新转回0的地址区域,有点像“驴拉磨”的意思......
环列队列的是逻辑上将数组元素q[0]与q[MAXN-1]连接,形成一个存放队列的环形空间。
环形队列设计的重要部分是,确定队列的状态,即队列时空还是满状态。根据上面介绍的存储顺序,数据存储时,队列尾指针移动,头指针不动,数据读取时,头指针移动,尾指针不动,但是如果从单纯的“tail==head”还无法得出是处于空还是满状态,需要加些判断手段:
- 附件标志法
1、tail追上head时,tag=1,队列为满状态
2、head追上tail时,tag=0,队列为空状态
- 预留位置法
在存储数据时,最后一个位置与队列头预留至少一个单位的空间
1、head==tail 队列为空
2、(tail+1)% MAXN ==head 队列满
c语言代码实现
环形队列的原理也算比较简单,弄清楚了原理之后,进行代码的编写。
- 方法1:附加标志法
先来定义个结构体:
typedef struct Squeue { /*顺序循环队列的类型定义*/ DataType queue[QUEUESIZE]; int front, rear; /*队头指针和队尾指针*/ int tag; /*队列空、满的标志*/ } SCQueue;
初始化队列:
/*将顺序循环队列初始化为空队列,需要把队头指针和队尾指针同时置为0,且标志位置为0*/ void InitQueue(SCQueue *SCQ) { SCQ->front = SCQ->rear = 0; /*队头指针和队尾指针都置为0*/ SCQ->tag = 0; /*标志位置为0*/ }
判断是否为空队列:
/*判断顺序循环队列是否为空,队列为空返回1,否则返回0*/ int QueueEmpty(SCQueue SCQ) { if (SCQ.front == SCQ.rear && SCQ.tag == 0) /*队头指针和队尾指针都为0且标志位为0表示队列已空*/ return 1; else return 0; }
插入元素:
/*将元素e插入到顺序循环队列SQ中,插入成功返回1,否则返回0*/ int EnQueue(SCQueue *SCQ, DataType e) { if (SCQ->front == SCQ->rear && SCQ->tag == 1) /*在插入新的元素之前,判断是否队尾指针到达数组的最大值,即是否上溢*/ { printf("顺序循环队列已满,不能入队!"); return 1; } else { SCQ->queue[SCQ->rear] = e; /*在队尾插入元素e */ SCQ->rear = (SCQ->rear + 1) % QUEUESIZE; //SCQ->rear = SCQ->rear + 1; /*队尾指针向后移动一个位置,指向新的队尾*/ } if (SCQ->rear == SCQ->front) { SCQ->tag = 1; /*队列已满,标志位置为1 */ } return SCQ->tag; }
读取元素:
/*删除顺序循环队列中的队头元素,并将该元素赋值给e,删除成功返回1,否则返回0*/ int DeQueue(SCQueue *SCQ, DataType *e) { if (QueueEmpty(*SCQ)) /*在删除元素之前,判断队列是否为空*/ { printf("顺序循环队列已经是空队列,不能再进行出队列操作!"); return 0; } else { *e = SCQ->queue[SCQ->front]; /*要出队列的元素值赋值给e */ SCQ->front = (SCQ->front + 1) % QUEUESIZE; /*队头指针向后移动一个位置,指向新的队头元素*/ SCQ->tag = 0; /*删除成功,标志位置为0 */ } if (SCQ->rear == SCQ->front) { SCQ->tag = 0; /*队列已空,标志位置为0 */ } return SCQ->tag; }
先来对这种方法进行测试:
void main() { SCQueue Q; /*定义一个顺序循环队列*/ int e; /*定义一个字符类型变量,用于存放出队列的元素*/ int a[] = {1, 2, 3, 4,5}, i; InitQueue(&Q); /*初始化顺序循环队列*/ /*将数组中的4个元素依次入列*/ for (i = 0; i < sizeof(a) / sizeof(a[0]); i++) EnQueue(&Q, a[i]); /*将顺序循环队列中的元素显示输出*/ printf("队列中元素:"); // DisplayQueue(Q); /*将顺序循环队列中的队头元素出队列*/ i = 0; while (!QueueEmpty(Q)) { printf("队头元素第%d次出队\n", ++i); DeQueue(&Q, &e); printf("出队的元素:"); printf("%d\n",e); // PrintData(e); } }
测试结果:
- 预留位置法
代码与第一种方法区别不大,主要在空、满状态的判断上,代码如下:
/*将顺序循环队列初始化为空队列,需要把队头指针和队尾指针同时置为0,且标志位置为0*/ void InitQueue(SCQueue *SCQ) { SCQ->front = SCQ->rear = 0; /*队头指针和队尾指针都置为0*/ SCQ->tag = 0; /*标志位置为0*/ } //获取队列长度 int QueueLength(SCQueue *SCQ) { return (SCQ->rear - SCQ->front + QUEUESIZE) % QUEUESIZE; } /*判断顺序循环队列是否为空,队列为空返回1,否则返回0*/ int QueueEmpty(SCQueue SCQ) { if (SCQ.front == SCQ.rear) /*队头指针和队尾指针都为0且标志位为0表示队列已空*/ return 1; else return 0; } /*将元素e插入到顺序循环队列SQ中,插入成功返回1,否则返回0*/ int EnQueue(SCQueue *SCQ, DataType e) { /*在插入新的元素之前,判断是否队尾指针到达数组的最大值,即是否上溢*/ if ((SCQ->rear + 1) % QUEUESIZE == SCQ->front) { printf("顺序循环队列已满,不能入队!"); return 0; } SCQ->queue[SCQ->rear] = e; /*在队尾插入元素e */ SCQ->rear = (SCQ->rear + 1) % QUEUESIZE; /*队尾指针向后移动一个位置,指向新的队尾*/ printf("数据已插入"); } /*删除顺序循环队列中的队头元素,并将该元素赋值给e,删除成功返回1,否则返回0*/ int DeQueue(SCQueue *SCQ, DataType *e) { if (QueueEmpty(*SCQ)) /*在删除元素之前,判断队列是否为空*/ { printf("顺序循环队列已经是空队列,不能再进行出队列操作!"); return 0; } else { *e = SCQ->queue[SCQ->front]; /*要出队列的元素值赋值给e */ SCQ->front = (SCQ->front + 1) % QUEUESIZE; /*队头指针向后移动一个位置,指向新的队头元素*/ return 1; } }
main函数与上面相同:
void main() { SCQueue Q; /*定义一个顺序循环队列*/ int e; /*定义一个字符类型变量,用于存放出队列的元素*/ int a[] = {1, 2, 3, 4,5}, i; InitQueue(&Q); /*初始化顺序循环队列*/ /*将数组中的4个元素依次入列*/ for (i = 0; i < sizeof(a) / sizeof(a[0]); i++) EnQueue(&Q, a[i]); /*将顺序循环队列中的元素显示输出*/ printf("队列中元素:"); // DisplayQueue(Q); /*将顺序循环队列中的队头元素出队列*/ i = 0; while (!QueueEmpty(Q)) { printf("队头元素第%d次出队\n", ++i); DeQueue(&Q, &e); printf("出队的元素:"); printf("%d\n",e); //PrintData(e); } }
测试结果:
相比较上面的测试结果,小伙伴们有没有发现什么不同之处呢,我们main函数想把5个元素写入队列,实际上只写进去了4个,原因在与,我们预留了一个单元空间,用于判断队列空或者满的状态。
本次的介绍就到这里啦,下章介绍:环形队列在单片机中的应用,欢迎大家持续关注嵌入式实验基地,来这里还可以学习HAL库+cubemx的更多精彩内容哦!
如果你觉得对自己有帮助的话,给个赞,点个关注,点个在看,感谢前进的道路上有你的陪伴!
所有公众号文章资料源码已上传,关注公众号回复资料即可获取哦,欢迎加群一起炸起来!
这篇关于循环队列原理及在单片机串口通讯的应用(一)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-13怎么通过 JavaScript 或其他编程语言来实现监听屏幕高度变化功能?-icode9专业技术文章分享
- 2024-11-12聊聊我们那些年用过的表达式引擎组件
- 2024-11-12让项目数据更有说服力:五款必备数据可视化管理工具推荐
- 2024-11-12人到一定年纪,要学会远离多巴胺
- 2024-11-12解读:精益生产管理的目的是什么?如何操作?
- 2024-11-12Sku预研作业
- 2024-11-12文心一言API密钥:分步申请指南
- 2024-11-12初学者指南:轻松掌握后台交互
- 2024-11-12从零开始学习:封装基础知识详解
- 2024-11-12JSON对象入门教程:轻松掌握基础用法