1.前言:
在C语言实现栈和队列-CSDN博客中,我们介绍了队列这一线性数据结构,它有着“先进先出”的性质,我们可以不断的出入数据。那现在有这么一个场景:假设一餐馆有十张桌子,先进店的客人先点餐,离开的客人可以给新来的客人让出桌位,一共就是十张桌子,循环进出客人点菜(假设先进的客人先离开)。那我们可不可以用队列实现它呢?显然一般的队列并不能体现“循环”,因此,我们想到了让队列首尾相接,即循环队列。
2.循环队列的概念
循环队列也是一种线性数据结构。相较于队列,不同点在于该队列可以循环使用已有的未被使用的空间。且其空间大小固定,依靠循环才能不断进出数据。
图中一看确实如此,但如果我们细想:如果tail指向的是队尾数据,循环队列中没有数据时,head=tail,只有一个数据时,head仍等于tail。这肯定是不被允许的。因此我们不能让tail指向队尾数据,而应该让其指向队尾数据的下一个,但细想又不对,因为没有数据和满数据两种情况下都有head=tail,我们称之为假溢出。为解决这个问题
①我们会多申请一个空间,即使满的情况下,head也不等于tail。如图:
②在队列中加入一个计数器,通过计数器判断循环队列是否为空/满。
要求循环队列的功能:出入数据、获取队首/队尾数据、判空。思路已经有了,我们用代码实现它。
3.实现循环队列
我们会用数组、循环链表两种方法实现。
①数组方法
定义循环队列:
typedef struct
{
int* p; //malloc申请的空间返回的是首空间的地址,用指针接收
int head; //注意head tail不是指针,是数组的下标
int tail;
int num; //循环队列固定的空间大小
} MyCircularQueue;
MyCircularQueue* myCircularQueueCreate(int k) //初始化循环队列,k是需要的空间大小
{
MyCircularQueue* tmp = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
assert(tmp);
tmp->p = (int*)malloc(sizeof(int)*(k+1));
tmp->head = tmp->tail = 0; //此时head tail均为0
tmp->num =k ; //num记录空间大小k,下面有大用
return tmp;
}
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) //入数据
{
if ((obj->tail + 1) % (obj->num + 1) == obj->head) //判满,满了则不能入数据
{
return false;
}
else
{
obj->p[obj->tail] = value; //向tail处插入数据
obj->tail = (obj->tail + 1) % (obj->num + 1); //tail后移1个空间
return true;
}
}
bool myCircularQueueDeQueue(MyCircularQueue* obj) //删除还要判空,空的当然不能删数据
{
if (obj->head==obj->tail)
{
return false;
}
else
{
obj->head = (obj->head+ 1) % (obj->num + 1);
return true;
}
}
int myCircularQueueFront(MyCircularQueue* obj) //获取队头数据
{
if (obj->head != obj->tail)
return obj->p[obj->head];
else
return -1;
}
int myCircularQueueRear(MyCircularQueue* obj) //获取队尾数据
{
if (obj->head != obj->tail)
{
int a = (obj->tail + obj->num) % (obj->num + 1);
return obj->p[a];
}
else
return -1;
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj) //判空
{
if (obj->head == obj->tail)
{
return true;
}
else
return false;
}
void myCircularQueueFree(MyCircularQueue* obj) //销毁循环队列
{
free(obj->p);
free(obj); //先销毁数组再销毁循环队列
obj->head = obj->tail = obj->num = 0;
}
要明白num的作用,额外注意要循环的特殊情况:
②链表方法(循环单链表)
思想和数组一样,区别在于创建循环链表(双向链表更加复杂,单链表已经足够了)。
结点:(这里和队列的创建是一样的)
typedef int definetype;
typedef struct node
{
definetype a;
struct node* next;
}Node;
typedef struct
{
Node* front;
Node* prere; //多了一个指针,指向最后一个数据
Node* rear; //还是指向最后一个数据的下一个
int num;
} MyCircularQueue;
因为是单链表,所以不能像数组一样直接得队尾数据,加一个指针指向rear的前一个结点更方便。
MyCircularQueue* myCircularQueueCreate(int k)
{
MyCircularQueue* tmp = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
assert(tmp);
int i = 0;
Node*c=NULL, *d=NULL;
while (i!=(k+1)) //创建k+1个结点的单链表
{
if (i == 0)
{
c = (Node*)malloc(sizeof(Node));
assert(c);
c->next = NULL;
tmp->front = tmp->rear=tmp->prere = c;
i++;
}
else
{
d = (Node*)malloc(sizeof(Node));
c->next = d;
c = d;
i++;
if (i == k+1)
{
c->next = tmp->front; //首尾相连
}
}
}
tmp->num = k;
return tmp;
}
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
if (obj->rear->next == obj->front)
{
return false;
}
else
{
definetype*we=&obj->rear->a ;
*we = value; //修改数据
obj->prere = obj->rear;
obj->rear = obj->rear->next;
return true;
}
}
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
if (obj->front == obj->rear)
{
return false;
}
else
{
obj->front = obj->front->next;
return true;
}
}
int myCircularQueueFront(MyCircularQueue* obj) { //返回队头队尾数据首先需要判空
if (obj->front == obj->rear)
return -1;
else
return (obj->front)->a;
}
int myCircularQueueRear(MyCircularQueue* obj) {
if (obj->front == obj->rear)
return -1;
else
return (obj->prere)->a;
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
if (obj->front == obj->rear)
{
return true;
}
else
return false;
}
bool myCircularQueueIsFull(MyCircularQueue* obj) {
if (obj->rear->next == obj->front)
{
return true;
}
else
return false;
}
void myCircularQueueFree(MyCircularQueue* obj) //销毁队列要注意避免出现野指针
{
Node* tmp = NULL;
while (obj->num!=-1) //注意要销毁k+1个结点
{
tmp= obj->front->next;
free(obj->front);
obj->front = tmp;
obj->num--;
}
obj->front = obj->rear = obj->prere = NULL;
obj->num = 0;
free(obj);
}
总结:循环队列要求循环利用固定的空间,并且无论哪种方法,都要多申请一个空间。一定要区别不同情况,并且考虑完全,才能解决问题。