循环队列原理及在单片机串口通讯的应用(一)

30 篇文章 9 订阅
19 篇文章 2 订阅

前言

  当代码,不再是简单的完成需求,对代码进行堆砌,而是开始思考如何写出优美代码的时候,我们的代码水平必然会不断提升,今天,咱们来学习环形队列结构。

环形队列的基本概念

  相信对数据结构有过接触的小伙伴,对队列肯定不会陌生,队列相对来说是比较简单的数据结构,典型特点是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的更多精彩内容哦!

  如果你觉得对自己有帮助的话,给个赞,点个关注,点个在看,感谢前进的道路上有你的陪伴!

  所有公众号文章资料源码已上传,欢迎加群一起炸起来!

  • 4
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
单片机串口通信循环队列是一种解决数据传输问题的数据结构。在单片机的串口通信,数据的传输速度常常比较快,而处理数据的速度较慢,因此需要一个缓冲区来存储接收到的数据。循环队列是一种适合此场景的数据结构。 循环队列的特点是使用数组来实现队列,其定义了队列的头指针和尾指针,它们分别指向队列的头部和尾部。当有数据存储时,队列尾指针不断增加,直到空间用完。当数据出队列时,队列头指针不断增加,直至和队列尾指针相同时,表示所有数据完成出队列。 在单片机串口通信循环队列可以解决"假溢出"的问题,即使数组仍有空间可用,但无法继续入队的情况。通过清空队列或重新接收数据,可以解决这个问题。 下面是一个使用循环队列实现单片机串口通信的代码示例: ``` #define MAX_SIZE 100 // 定义队列的最大容量 typedef struct { int data[MAX_SIZE]; // 用数组存储队列的元素 int front; // 队列头指针 int rear; // 队列尾指针 } SCQueue; void InitQueue(SCQueue *q) { q->front = 0; // 初始化队列头指针为0 q->rear = 0; // 初始化队列尾指针为0 } int QueueEmpty(SCQueue q) { return (q.front == q.rear); // 队列为空的条件是头指针等于尾指针 } void EnQueue(SCQueue *q, int x) { if ((q->rear + 1) % MAX_SIZE == q->front) { printf("队列已满,无法入队\n"); return; } q->data[q->rear = x; // 将元素入队 q->rear = (q->rear + 1) % MAX_SIZE; // 更新队尾指针 } int DeQueue(SCQueue *q) { if (q->front == q->rear) { printf("队列为空,无法出队\n"); return -1; // 返回一个特殊值表示出错 } int x = q->data[q->front]; // 获取队头元素 q->front = (q->front + 1) % MAX_SIZE; // 更新队头指针 return x; } void SerialCommunication() { SCQueue q; int data[] = {1, 2, 3, 4, 5}; int i, e; InitQueue(&q); // 初始化队列 for (i = 0; i < sizeof(data) / sizeof(data

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小飞哥玩嵌入式

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值