(本题取自LeetCode622.设计循环队列)
循环队列是指一种线性的数据结构,该结构遵循先进先出的原则,并且队尾则被连接在队首之后以形成一个循环。
循环队列可以循环使用队列之前用过的空间,普通队列的头元素前的节点不能再使用,而循环队列可以在队列没有满之前循环的使用空间。
对于循环队列我们需要支持如下的操作:
1.构造一个队列长度为k的循环队列
2.从队首获取元素,若队列为空则返回-1
3.获取队尾元素,若队列为空则返回-1
4.向循环队列插入一个元素,若成功插入则返回true
5.从循环队列中删除一个元素,如果成功删除则返回true
6.检查队列是否为空
7.检查队列是否为满
8.释放循环队列使用的空间
不难看出,循环队列的特点就是循环(经典废话),而链表利用next指针很容易就能实现这种结构。不过今天我们采用顺序表的方式来实现循环链表。
循环队列的判空条件很容易就能想到,当头尾都指向同一块空间时,这个队列就是空的。
而当不断插入数据时,tail向后移动,但可以看出,tail表示的是队尾的下一块空间,而非直接表示队尾。
所以,当队列继续插入,直到满的时候,假设tail可以回到第一个位置,此时的tail会指向哪里呢?
可以看到,此时tail与head又指向了同一块空间,那么此时的问题就是,队列为空时也是这样的。如果不加以判断,就无法区分队列是满还是空了,所以这里我们有两种解决办法:
1.在队列中加一个变量size用来记住当前队列中元素的个数,当size等于0表示队列为空,当size等于k表示队列已满。
2.当队列中仍有一个空位时就判定队列已满,这样当tail指向head前一块空间时就认定队列已满,不会出现误判的情况了。
这里我们采用第一种方式解决,这种解决方式思路更加简单易懂。
由于是oj题,所以如下的实现过程中 assert断言 与 判断malloc返回的指针是否有效 这里省略了,但在平常的实现中应该加上。并且head与tail所说的指向是指
typedef struct {
int* a;//指向存储队列的空间
int head;//记录队列头的位置
int tail;//记录队列尾的位置
int k;//队列的长度
int size;//记录队列包含的元素个数
} MyCircularQueue;
这里我们需要一个指针a方便指向动态开辟的内存空间,由于k是作为参数传入的,所以我们要建立一个变量保存下来。其次还需要两个数head与tail保存队列头与尾的位置。最后还需要一个size来记录队列中包含的元素个数。
MyCircularQueue(k)
: 构造器,设置队列长度为 k 。MyCircularQueue* myCircularQueueCreate(int k)
{
MyCircularQueue* myqueue=(MyCircularQueue*)malloc(sizeof(MyCircularQueue));
myqueue->a=(int*)malloc(sizeof(int)*k);
myqueue->k=k;
myqueue->head=myqueue->tail=0;
myqueue->size=0;
return myqueue;
}
我们使用malloc创建一块动态的空间,并且对其成员进行初始化即可,最后返回创建好的循环队列即可。
enQueue(value)
: 向循环队列插入一个元素。如果成功插入则返回真。bool myCircularQueueEnQueue(MyCircularQueue* obj, int value)
{
if(myCircularQueueIsFull(obj))
return false;
obj->a[obj->tail]=value;
obj->tail=(obj->tail+1)%(obj->k);
obj->size++;
return true;
}
首先我们调用判断队列是否为满的函数,若队列已满则返回false。否则在队尾插入数据。由于tail指向的是队尾的下一块空间,所以在插入时直接插入元素即可,不需要先向后移动tail再插入。
然后让tail指向下一块空间即可,但是要注意,若tail已经指向第k块空间,这时候tail应该重新指向第一块空间,否则会越界。
所以这里我们对tail+1模k,这样若tail越界,也会因为模k重新指向第一块空间,也就完成了循环的操作。
最后,size自增,完成插入,返回true。
deQueue()
: 从循环队列中删除一个元素。如果成功删除则返回真。bool myCircularQueueDeQueue(MyCircularQueue* obj)
{
if(myCircularQueueIsEmpty(obj))
return false;
obj->head=(obj->head+1)%(obj->k);
obj->size--;
return true;
}
同样先用函数进行判断,若队列为空则返回false。否则直接将头向前移动一位,同时注意取模。
最后size自减,完成删除,返回true。
Front
: 从队首获取元素。如果队列为空,返回 -1 。int myCircularQueueFront(MyCircularQueue* obj)
{
if(myCircularQueueIsEmpty(obj))
return -1;
return obj->a[obj->head];
}
用判空函数进行判断,若队列为空则返回-1,否则返回队头元素即可。
Rear
: 获取队尾元素。如果队列为空,返回 -1 。int myCircularQueueRear(MyCircularQueue* obj)
{
if(myCircularQueueIsEmpty(obj))
return -1;
return obj->a[(obj->tail+obj->k-1)%obj->k];
}
同样,先进行判空,若队列为空返回-1,否则返回队头元素即可。
isEmpty()
: 检查循环队列是否为空。bool myCircularQueueIsEmpty(MyCircularQueue* obj)
{
return obj->size == 0;
}
直接利用size是否为0判断即可。
isFull()
: 检查循环队列是否已满。bool myCircularQueueIsFull(MyCircularQueue* obj)
{
return obj->size == obj->k;
}
直接利用size是否等于k即可。
void myCircularQueueFree(MyCircularQueue* obj)
{
free(obj->a);//释放循环队列
free(obj);//释放结构体
}
注意这里的释放不仅仅要释放结构体本身,也要释放循环队列的空间。
typedef struct {
int* a;
int head;
int tail;
int k;
int size;
} MyCircularQueue;
bool myCircularQueueIsEmpty(MyCircularQueue* obj);
bool myCircularQueueIsFull(MyCircularQueue* obj);
MyCircularQueue* myCircularQueueCreate(int k) {
MyCircularQueue* myqueue=(MyCircularQueue*)malloc(sizeof(MyCircularQueue));
myqueue->a=(int*)malloc(sizeof(int)*k);
myqueue->k=k;
myqueue->head=myqueue->tail=0;
myqueue->size=0;
return myqueue;
}
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
if(myCircularQueueIsFull(obj))
return false;
obj->a[obj->tail]=value;
obj->tail=(obj->tail+1)%(obj->k);
obj->size++;
return true;
}
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
if(myCircularQueueIsEmpty(obj))
return false;
obj->head=(obj->head+1)%(obj->k);
obj->size--;
return true;
}
int myCircularQueueFront(MyCircularQueue* obj) {
if(myCircularQueueIsEmpty(obj))
return -1;
return obj->a[obj->head];
}
int myCircularQueueRear(MyCircularQueue* obj) {
if(myCircularQueueIsEmpty(obj))
return -1;
return obj->a[(obj->tail+obj->k-1)%obj->k];
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
return obj->size == 0;
}
bool myCircularQueueIsFull(MyCircularQueue* obj) {
return obj->size == obj->k;
}
void myCircularQueueFree(MyCircularQueue* obj) {
free(obj->a);
free(obj);
}