数据结构(11)队列之循环队列
前言
昨天说到在顺序队列中,如果按照“入队则尾指针加一,出队则头指针加一”的方式实现入队出队操作,就可能出现尾指针已越界,无法插入元素;队列实际上却仍有空间的“假溢出”现象。如图
为了充分利用空间,我们可以再让尾指针循环回来,寻找插入的位置。这样,队列的这种头尾相接的顺序存储结构就被称为循环队列。
为了方便理解,书上常常将循环队列画成一个环状的结构,但是实际上它仍然是线性的。
这样,在循环队列中,判断队列已满的条件就从“查看rear值是否大于等于队列最大容量”,变成了“查看rear值是否与fount值相等”。
循环队列的实现
按照现在的设计,我们进行插入操作(新元素入队)时,需要比较rear值与fount值是否相等,如果相等说明队列已满,无法插入。但回想我们在初始化队列时,把fount值和rear值都初始化为0了。现在rear值与fount值相等就有两种情况:第一是队列是空的,可以插入元素;第二是队列已满,无法插入元素。如何区分这两种状态呢?主要有以下几种方法:
- 设置一个flag变量标志队列的状态,当flag为0时,说明队列未满;当flag为1时,说明队列已满。
- 与上一个类似,设置一个变量来记录队列内的元素个数,当元素个数小于队列最大容量时,说明队列为满;元素个数大于等于队列最大容量时,说明队列已满。(也可以不特别用变量来记录,而是用获取队列内元素个数的方法来获得当前的元素个数,主要看自己是怎么实现这个方法的。)
- 一个比较特别的方法是:少用一个元素空间,当队列还有最后一个空间时,我们就认为它已经满了。什么意思呢?即设队列最大容量为m,当队列内有m-1个元素时,就认为它满了。这时候会有三种情形:
在这种前提下,当(rear == fount)时,说明队列是空的。那么判定队满的条件是什么呢?假如只考虑到 1 和 2 两种情况,很明显就是( (rear+1) == fount);但是还有 3 这种情况,这时候fount和rear相差了整整一圈,需要解决的问题是:怎样让rear循环回来?
我首先想到的办法是:当rear值等于数组最大下标时,再把它设为0就行了。当然因为涉及到与fount的比较,是设为0还是-1,自增后设置还是设置后自增等等问题还需要详细考虑,能不能行也还没有实际去做,这里就只记录下思路。
现在说下正常的方法:首先需要知道,当数A与某个数B求余时(A % B),得到的结果一定是在 0~(B-1) 这个范围内。利用求余运算的特性,我们不让rear单纯地自增,而是自增后与队列最大容量求余( (rear + 1) % MAXSIZE)。这样,当rear小于数组最大下标(MAXSIZE-1)时,rear仍是自增的;当rear值等于数组最大下标(MAXSIZE-1)时,rear就被设成了0; rear的值始终在 0~(MAXSIZE-1)范围内循环。那么,判断队满的条件就是:((rear+1)%MAXSIZE == fount)。
循环队列求当前元素个数
循环队列在求当前元素个数(即长度)时,同样有三种情形:
第一种是rear > fount,第二种是 rear < fount,第三种则是 rear == fount。
毫无疑问,如果是第一第三种情况时,只需要(rear - fount)就可以得到当前元素个数了。但是在第二种情况中,(rear - fount)得到的是负值,由于此时fount和rear相差了整整一圈,实际上应该是( (MAXSIZE+rear) - fount)。
现在使用if判断情况然后调用不同的算式固然能得到答案,但有没有能统一三种情况的方法呢?求余运算又登场了。假如我们都使用(MAXSIZE + rear - fount)这个算式,可以发现在第一第三种情况中,算式值只是多了一个MAXSIZE值而已,我们让它与MAXSIZE求余,所得的结果就是(rear - fount)。而在第二种情况中,由于算式值小于MAXSIZE,所得的结果仍是(MAXSIZE + rear - fount)。
这样,通用的计算公式就是:(rear - fount + MAXSIZE)%MAXSIZE
全部代码
SeqQueue.h
#ifndef SeqQueue_h
#define SeqQueue_h
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#define ElemType int
#define MAXSIZE 8
typedef struct Queue{
ElemType *base;
int fount;
int rear;
}Queue;
//初始化
void InitQueue(Queue *Q);
//入队
void EnQueue(Queue *Q,ElemType x);
//出队
void DeQueue(Queue *Q);
//展示
void ShowQueue(Queue *Q);
//获取队首元素
void GetHead(Queue *Q,ElemType *x);
//求长度
int GetLength(Queue *Q);
//清除
void CleatQueue(Queue *Q);
//摧毁
void DestoryQueue(Queue *Q);
#endif /* SeqQueue_h */
SeqQueue.c
#include "SeqQueue.h"
//初始化
void InitQueue(Queue *Q){
//开辟空间
Q->base = (ElemType *)malloc(sizeof(ElemType)*MAXSIZE);
assert(Q->base != NULL);
//队头指针和队尾指针指向0下标
Q->fount = Q->rear = 0;
}
//入队
void EnQueue(Queue *Q,ElemType x){
//判断队列是否满
if ((Q->rear + 1)%MAXSIZE == Q->fount) {
printf("队列已满\n");
return;
}
Q->base[Q->rear] = x;
Q->rear = (Q->rear+1)%MAXSIZE;
}
//出队
void DeQueue(Queue *Q){
//判断队列是否空
if (Q->fount == Q->rear) {
printf("队列已空\n");
return;
}
Q->fount = (Q->fount+1)%MAXSIZE;
}
//展示
void ShowQueue(Queue *Q){
for (int i = Q->fount; i != Q->rear;) {
printf("%4d",Q->base[i]);
i = (i+1)%MAXSIZE;
}
printf("\n");
}
//获取队首元素
void GetHead(Queue *Q,ElemType *x){
//判断队列是否空
if (Q->fount == Q->rear) {
printf("队列已空\n");
return;
}
*x = Q->base[Q->fount];
}
//求长度
int GetLength(Queue *Q){
return (MAXSIZE + Q->rear - Q->fount) % MAXSIZE;
}
//清除
void CleatQueue(Queue *Q){
Q->fount = Q->rear = 0;
}
//摧毁
void DestoryQueue(Queue *Q){
free(Q);
Q->base = NULL;
}
Main.c
#include "SeqQueue.h"
int main(int argc, const char * argv[]) {
Queue Q;
InitQueue(&Q);
//入队
for (int i = 0; i < 7; i ++) {
EnQueue(&Q, i);
}
ShowQueue(&Q);
//出队
DeQueue(&Q);
EnQueue(&Q, 10);
ShowQueue(&Q);
DeQueue(&Q);
EnQueue(&Q, 20);
ShowQueue(&Q);
int len = GetLength(&Q);
printf("len = %d\n",len);
return 0;
}