思考
1.如何实现
循环队列首先需要思考的是,基于什么来实现。那么我们可以想到数组和链表。对于链表,单链表呢,我们不方便找到上一个节点,需要遍历整个链表去找到,且他的时间复杂度为O(N), 所以,单双链表我们均不考虑。那么就只剩下数组了。对于数组,我们可以创建两个变量head和tail用来存储头节点和尾节点,所以访问上一个节点我们就可以直接通过-1来访问了。
2.数组的满与空冲突问题
我们选择用数组去实现这个问题,假设我们共有四个空间,当我们试验后发现,在这个数组中数组中元素满的情况为head=tail,数组为空的情况仍为head=tail。这时就会出现假溢出的情况,对于这种情况我们可以有两种解决方法。
方法一:我们可以在结构体中增加一个元素size用来存储当前数组中的元素个数,当size=0时数组为空,当size=k时,数组为满。
方法二:多创建一个空间,使空间总数为k+1,这样,当tail+1=head时证明数组为满,当tail=head时证明数组为空。
我们选择方法二来解答这道题。
1.结构体创建
typedef struct
{
int*a;
int head;//数组的第一个元素
int tail;//数组的尾元素的下一个元素
int k;
} MyCircularQueue;
2.结构体的初始化
首先,我们需要先开辟一块空间来存储结构体,然后再开辟一块空间存储数组,因为我们的数组中需要多开辟一个空间用来防止假溢出,所以数组中元素个数为k+1,所以开辟空间大小为int*(k+1)。再将head与tail等于零,k等于题中所给的k值。
MyCircularQueue* myCircularQueueCreate(int k)
{
MyCircularQueue*obj=(MyCircularQueue*)malloc(sizeof(MyCircularQueue));
obj->a=(int*)malloc(sizeof(int)*(k+1));//多创建出来一个空间防止假溢出
obj->head=0;
obj->tail=0;
obj->k=k;
return obj;
}
3.myCircularQueueIsEmpty()检查循环队列是否为空
如上文所说,判断循环队列为空的条件就是head=tail。
bool myCircularQueueIsEmpty(MyCircularQueue* obj)
{
return obj->tail==obj->head;
}
4.myCircularQueueIsFull()检查循环队列是否已满
如上文,判断循环队列是否已满的条件就是tail+1=head,但是,我们考虑一下极端情况。
head
1 | 2 | 3 | 4 |
tail
此时,tail+1!=head,所以我们就需要借助取余符号,我们将所有tail+1都对k+1进行取余,当tail+1小于k+1时对于结果并没有影响,但是,当tail+1=5时,我们就可以应用取余的方法使tail+1=head,也就是完成了题目中要求的循环。
bool myCircularQueueIsFull(MyCircularQueue* obj)
{
return (obj->tail+1)%(obj->k+1)==obj->head;
}
5.boolmyCircularQueueEnQueue() 向循环队列插入一个元素
想要在循环队列中插入元素,首先我们需要调用myCircularQueueIsFull()函数,判断循环队列是否已满,如果满了,则返回false。如果还有空位,我们直接将元素赋值给tail,再++tail。但是如果是如图的特殊情况,我们则需要在下面将tail对k+1进行取余,这样就能使在尾部的tail重新回到头部。
bool myCircularQueueDeQueue(MyCircularQueue* obj)
{
if(myCircularQueueIsEmpty(obj))
{
return false;
}
++obj->head;
obj->head%=(obj->k+1);
return true;
}
6.myCircularQueueFront()从队首获取元素
首先,我们需要调用myCircularQueueIsEmpty()函数判断这个循环队列是否为空队列,如果为空则返回-1,如果不为空队列则直接返回队列中下标为head的元素。
int myCircularQueueFront(MyCircularQueue* obj)
{
if(myCircularQueueIsEmpty(obj))
{
return -1;
}
return obj->a[obj->head];
}
7.myCircularQueueRear()获取队尾元素
首先,我们需要调用myCircularQueueIsEmpty()函数判断这个队列是否为空队列,如果为空则返回-1。由于tail是队尾元素的下一个元素,所以想要获取队尾元素,就需要将tail-1。那么如下图的特殊情况我们应当如何才能回到数组的尾呢。与上面的向循环队列插入元素一样,我们借助取余的方法,让tail-1再加k+1,再对k+1进行取余,这样,对小于k+1的数没有任何影响,且当tail=0时,可以让tail成功的循环回去,访问到队尾元素。
head
1 | 2 | 3 |
tail
int myCircularQueueRear(MyCircularQueue* obj)
{
if(myCircularQueueIsEmpty(obj))
{
return -1;
}
return obj->a[((obj->tail-1)+(obj->k+1))%(obj->k+1)];
}
3. myCircularQueueFree()空间释放
想要释放空间,我们应当先从数组开始释放,数组释放完毕后再对结构体进行释放。
void myCircularQueueFree(MyCircularQueue* obj)
{
free(obj->a);
free(obj);
}
大家有兴趣的可以自行尝试一下哦~