循环队列的两种实现方式

线性表有顺序存储和链式存储,队列作为一种特殊的钱性表,也同样存在这两种存储方式。

队列顺序存储的不足

我们假设一个队列有n个元素,则顺序存储的队列需建立一个大于n的数组,并把队列的所有元素存储在数组的前n个单元,数组下标为0的一端即是队头。所谓的入队列操作,其实就是在队尾追加一个元素,不需要移动任何元素,因此时间复杂度为O(1),如下图所示:
在这里插入图片描述
与栈不同的是,队列元素的出列是在队头,即下标为0的位置,那也就意味着队列中的所有元素都得向前移动,以保证队列的队头,也就是下标为0的位置不为空,此时时间复杂度为 O(n),如图下图所示:
在这里插入图片描述
可是为什么出队列时一定要全部移动呢,如果不去限制队列的元素必须存储在数组的前n个单元这一条件,出队的性能就会大大增加。也就是说,队头不需要一定在下标为0的位置,如下图所示:
在这里插入图片描述
引入两个指针,front 指针指向队头元素,rear 指针指向队尾元素的下一个位置,这样当 front 等于 rear 时,此队列不是还剩一个元素,而是空队列。假设是长度为5的数组,初始状态,空队列如下图的左图所示, front 与 rear 指针均指向下标为0的位置。然后入队 a1、a2、a3、a4, front 指针依然指向下标为0的位置,而 rear 指针指向下标为4的位置,如下图的右图所示:
在这里插入图片描述
出队 a1、a2,则 front 指针指向下标为2的位置, rear 不变,如下图的左图所示,再入队 ,此时 front 不变, rear 指针移动到数组之外。如下图的右图所示:
在这里插入图片描述
假设这个队列的总个数不超过5个,但目前如果接着人队的话,因数组末尾元素已经占用,再向后加,就会产生数组越界的错误,可实际上,我们的队列在下标为0和1的地方还是空闲的,我们把这种现象叫做“假滥出”。解决假溢出的办法就是后面满了,就再从头开始,也就是头尾相接的循环。我们把队列的这种头尾相接的顺序存储结构称为循环队列。

循环队列

上图中溢出的rear指针可以指向下标为0的位置,这样就不会造成指针指向不明的问题了,如下图所示:
在这里插入图片描述
接着入队a6,将它放置于下标为0处, rear 指针指向下标为1处,如下图的左图所示。若再入队a7,则 rear 指针就与front指针重合,同时指向下标为2的位置,如下图的右图所示:
在这里插入图片描述
此时问题又出来了,我们刚才说,空队列时, front 等于 rear,现在当队列满时,也是 front 等于 rear ,那么如何判断此时的队列究竟是空还是满呢?
方法1:
首先,我们很容易想到的一种方法就是用一个变量size来统计队列中有效元素个数。每次入队size加一,每次出队size减一。在我们每次出队和入队的时候,先判断front和rear是否相等,如果不相等直接执行入队出队操作;如果相等的话,判断front是否为0,如果为0说明队列为空,可以执行入队操作,不可以执行出队操作,如果不为0说明队列已满,可以执行出队操作,不可执行入队操作。
方法2:
方法2就是当队列空时,条件是 front = rear,当队列满时,我们保留一个元素空间,也就是说,队列满时,数组中还有一个空闲单元。如下图所示,我们就认为此队列已经满了,也就是说,我们不允许图上图中的右图情况出现。
在这里插入图片描述
由于 rear 可能比 front 大,也可能比 front 小,所以尽管它们只相差一个位置时就是满的情况,但也可能是相差整整一圈。所以若队列的最大尺寸为QueueSize,那么队列满的条件是(rear+1)%QueueSize == front(取模的目的是为了将两种情况一起考虑)。比如说,在上图的左图中,front = 0,rear = 4,(4+1)%5 = 0,所以此时队列满。在上图的右图中,front = 2,rear = 1,(1+1)%5 = 2,所以此时队列也是满的。而对于下图,front = 2,rear = 0,(0+1)%5=1,1≠2,所以此时队列并没有满。
在这里插入图片描述
当 rear > front 时,队列的长度为 rear-front。当 rear < front 时,队列的长度分为两段,一段是QueueSize-front,另一段是 0+rear,加在一起,队列的长度为rear-front+QueueSize,因此,通用的计算队列长度的公式为:(rear-front+QueueSize)%QueueSize

实现循环队列的代码

循环队列的顺序存储结构代码如下:

typedef int QElemType; 
typedef struct
{
	QElemType data[MAXSIZE]; 
	int front; /*头指针*/
	int rear; /*尾指针,若队列不空,指向队列尾元素的下一个位置*/
}SqQueue ;

循环队列的初始化代码如下:

/*初始化一个空队列Q*/
int InitQueue(SqQueue *Q)
{
	Q->front = 0;
	Q->rear = 0;
	return 1;
}

循环队列求队列长度代码如下:

/*返回队列的元素个数,也就是队列的当前长度*/
int QueueLength(SqQueue *Q)
{
	return (Q->rear - Q->front + MAXSIZE) % MAXSIZE;
}

循环队列的入队列操作代码如下:

int EnQueue(SqQueue *Q, QElemType e)
{
	if ((Q->real+1)%MAXSIZE == Q->front)/*队列满的判断*/
		return 0;
	Q->data[Q->rear+1] = e;
	Q->rear = (Q->rear+1) % MAXSIZE;
	return 1;
}

循环队列的出队列操作代码如下:

int DeQueue(SqQueue *Q, QElemType *e)
{
	if ((Q->real+1)%MAXSIZE == Q->front)/*队列满的判断*/
		return 0;
	*e = Q->data[Q->front];
	Q->front = (Q->front+1) % MAXSIZE;
	return 1;
}

总结:单是顺序存储,若不是循环队列,算法的时间性能是不高的,但循环队列又面临着数组可能会溢出的问题,队列的链式存储结构可以避免这个问题。

  • 6
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值