数据结构--队列--顺序循环队列的操作实现(C语言)


本文中涉及的完整代码及各操作测试代码均已提交至Gitee,大家可以点击链接参考。
鄙人乃是一介初学者,文中及代码中难免出错,请同志们批评指正!
在这里插入图片描述


🎄队列是个什么样的数据结构?

我们前面介绍过栈,栈乃是一个只有一个口的直筒子。那么队列,其实是一个两端开口的直筒子。
其实这里的队列就基本相当于我们生活中的队列。举例一个场景:我们最近经常要做核酸排长队,队伍从队头做完核酸退出队列,新来的人从队尾加入队列。这也是数据结构中队列的数据插入、删除的操作。我们来给队列下一个定义:

队列( queue )是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。

队列是一种先进先出(First In First Out)的线性表,简称FIFO。允许插入的一-端称为队尾,允许删除的一端称为队头。假设队列是q= ( a1, a2, … an),那么a1就是队头元素,而an是队尾元素。这样我们就可以删除时,总是从a1开始,而插入时,列在最后。这也比较符合我们通常生活中的习惯,排在第一一个的优先出列, 最后来的当然排在队伍最后。
image.png

🎄循环队列

在介绍循环队列之前,我们先来考虑最普通最容易想到的队列的实现流程。
1.入队:
我们假定有一个数组,这个数组可以放5个元素,其下标从0到4,其中下标为0的地方我们称之为队头。如果要往里面放入元素,就从队尾开始追加。队尾的位置是当前最后一个元素所在位置。如下图。
image.png
2.出队:
如果要出队列,因为只允许从队头开始删除元素,所以下标为0的元素被删除。为了能让队头不空,后面的元素需要向前移动,这和顺序表的表头删除操作一模一样,其时间复杂度为O(n)。
image.png
3.改善
上面操作因为需要把元素整体移动,如果元素较多,这种移动无疑会耗费大量的时间,影响性能。那能不能不移动元素,而是移动队头呢?当下表为0的元素删除后,队头从下标为0的位置移动到下标为1的位置。这样使得队列的性能大大提高。因为其时间复杂度变为O(1)了。
image.png
4.问题
为了表示队头、队尾的位置,我们在程序中设定两个变量:front、rear。规定front指向队头元素的位置;rear指向队尾元素的下一个位置。
image.png
如上图中左图,当队列为空时,front和rear指向同一个位置,那么在编程时,当front==rear时,我们可以判定队列为空队列。向空队列中继续放入元素,直到rear指向数组最后一个空位。
此时我们进行出队操作,但是只移动队头,即front的位置,当我们删除两个元素后,front指向了下标为2的位置,如下图左图:
image.png
但是,如果我们继续向队列中放入元素,那么依旧是从队尾进行插入,然而,当我们放入一个元素后,队尾指针rear竟然指向了数组之外,这就造成了数组越界的问题。可能你会想,既然如此,我们就规定rear的位置指向下标为4的位置的时候判定队列为满,不允许继续插入就好了。但是,rear指针告诉我们队列已满,实际上我们队列的前面两个地方还空着呢,队列并没有满,这不是造成了很大的空间浪费吗?实际上,这种情况被称为“假溢出”。那么为了解决这样的问题,需要同时考虑到时间复杂度和数组越界,该如何解决呢?

5.问题的解决:循环队列
那么解决上面的问题,我们可以这样想:既然后面满了,而前面还空着,为什么不让新增的元素从头开始存储呢?这样,整个本来是直筒子的队列形成了一个头尾相接的圆环。我们把队列的这种头尾相接的顺序存储结构称为循环队列。

image.png
入上图,当想要放入a5 元素时,我们将其放入到队列中的最后一个空位中,但是队尾指针指向了最最开始的队头,也就是下标为0的位置。这样数组访问有址可循,不会出现数组越界访问的问题。接下来,我们继续向里面放入元素:
image.png
可以看到,当我们放入a7时,队尾rear追上了队头front。因为我们之前说判断队列为空的条件是:rear == front,那这种情况下,也会将已经满了的队列判断为空队列,继续往里面放元素,那不是把之前的数据都给覆盖了吗?这可乱了套了!

基于这种问题的出现,设计数据结构的老前辈想出了这样一个点子:既然如此,我干脆舍弃一个空间,当队列中还剩一个空闲位置时就判断队列已满吧,如图1:
image.png
也就是说,当整个队列只剩下这一个空闲空间的时候,就判断队列已满。
6.新的问题和解决:
虽然这种循环队列的判满方式在逻辑上解决了队列满没满的问题,但是我们还是会比较纠结于如何去判断队列中只剩一个空闲位置。
对于图1中的队列满了的情况,我们可以使用front-rear == 1这个判断条件,但如果是下面图2这种情况呢?
image.png
因为front比rear小,而且相减之后的绝对值也相差蛮多,所以无法使用front-rear == 1这个判断条件。
这时考虑使用这样的一个条件:假定队列最大元素个数为maxSize,判满条件为:(rear + 1) % maxSize == front。
对于图1:rear=1 front=2,而(1+1)%5== 2 == front。
对于图2:rear=4 front=0,而(4+1)%5== 0== front。
两者都被判断为满。
总结一下,队列判空和判满的条件分别是:
队列为空:rear == front
队列为满:(rear + 1) % maxSize == front

有了上面的分析,下面让我们来具体实现一个顺序存储结构的循环队列吧!


🎄顺序循环队列的实现

首先我们来想想怎么样用C语言构建一个循环队列模型。
第一,我们要有存储数据的变量,这和顺序结构中一样,可以是一个指针来方便我们动态开辟内存。其中这个数据变量的类型应该是可改的,所以我们依旧使用typedef来给某个变量类型取个名字,方便以后程序的修改;
第二,我们要有队头和队尾的指针,这里考虑用两个int类型变量来方便我们指向队头队尾,front、rear
第三,需要一个变量来确定我们整个队列的大小,maxsize。
模型构建如下:

typedef int data_t;

typedef struct sequeue
{
   
	data_t* data;//存放数据
	int front;//队头
	int rear;//队尾
	int maxsize;//队列最大元素个数
}sequeue, 
  • 7
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值