循环队列
循环队列是一种使用有限数组来模拟队列的数据结构,它在逻辑上表现为先进先出(FIFO)的线性表,但在物理上则是利用数组来存储数据。循环队列的一个主要特点是它的头尾相连,形成一个环。当队列的尾部指针到达数组的最后一个元素时,它会自动跳回到数组的第一个元素开始继续存储数据,从而有效地利用了数组的存储空间。
循环队列的基本结构
循环队列通常需要两个指针来维护队列的状态:
- 队头指针(front):指向队列的第一个有效数据的位置。
- 队尾指针(rear):指向队列中最后一个有效数据的下一个位置,即下一个入队元素应该存放的位置。
循环队列的入队和出队操作
入队操作
- 对于循环队列,我们发现判断队满和队空的条件,均为rear==front,为了避免这种情况,通常会空出一个空间来检查队列是否已满。循环队列的满条件与普通的线性队列不同,它不能简单地判断
rear == 数组长度
,因为这样会浪费一个数组的存储空间。通常,循环队列的满条件是(rear + 1) % 数组长度 == front
。 - 如果队列未满,将新元素存储在
rear
所指向的位置,并将rear
指针向前移动一位,即rear = (rear + 1) % 数组长度
。
出队操作
- 检查队列是否为空。循环队列的空条件是
front == rear
。 - 如果队列不为空,取出
front
所指向的元素,并将front
指针向前移动一位,即front = (front + 1) % 数组长度
。
循环队列的优缺点
优点
- 节省空间:通过头尾相连的方式,有效地利用了数组的存储空间,避免了在队列的末尾还有大量空闲空间时,由于队列头部的空间已被占用,而无法入队新元素的问题。
- 操作高效:入队和出队操作的时间复杂度均为O(1),即在常数时间内完成。
缺点
- 空间浪费:循环队列在队列不满时,也会留下一个存储空间不被使用,以避免队空和队满的混淆。
循环队列代码实现
以力扣上的设计循环队列这道题为例
设计你的循环队列实现。 循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。
循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。
你的实现应该支持如下操作:
MyCircularQueue(k)
: 构造器,设置队列长度为 k 。Front
: 从队首获取元素。如果队列为空,返回 -1 。Rear
: 获取队尾元素。如果队列为空,返回 -1 。enQueue(value)
: 向循环队列插入一个元素。如果成功插入则返回真。deQueue()
: 从循环队列中删除一个元素。如果成功删除则返回真。isEmpty()
: 检查循环队列是否为空。isFull()
: 检查循环队列是否已满。
循环队列存储结构描述为:
typedef struct {
int *a;
int front;//队头指针
int rear;//队尾指针
int k;//数组中存放的个数
} MyCircularQueue;
MyCircularQueue(k)
: 构造器,设置队列长度为 k 。
MyCircularQueue* myCircularQueueCreate(int k) {
MyCircularQueue*obj=(MyCircularQueue*)malloc(sizeof(MyCircularQueue));
if(obj==NULL){
perror("malloc false");
return NULL;
}
//给数组开辟k+1个空间,空一个空间更好判断
//k+1为数组长度,但存放数据元素的个数是k,空一个空间更好判断队空和队满
obj->a=(int*)malloc(sizeof(int)*(k+1));
if(obj->a==NULL){
perror("malloc false");
return NULL;
}
obj->front=obj->rear=0;
obj->k=k+1;
return obj;
}
isEmpty()
: 检查循环队列是否为空。
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
//队头指针等于队尾指针时就是空队列
return obj->front==obj->rear;
}
isFull()
: 检查循环队列是否已满。
bool myCircularQueueIsFull(MyCircularQueue* obj) {
//队尾指针加一取模等于队头指针时就是满队列
return obj->front==(obj->rear+1)%(obj->k);
}
enQueue(value)
: 向循环队列插入一个元素。如果成功插入则返回真。
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
//队列满直接返回false
if(myCircularQueueIsFull(obj)){
return false;
}
//不满队尾指针先存值再加一
obj->a[obj->rear++]=value;
//然后队尾指针取模
obj->rear%=(obj->k);
return true;
}
deQueue()
: 从循环队列中删除一个元素。如果成功删除则返回真。
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
//队列空直接返回false
if(myCircularQueueIsEmpty(obj)){
return false;
}
//不空队头指针加一
obj->front++;
//然后队头指针取模
obj->front%=(obj->k);
return true;
}
Front
: 从队首获取元素。如果队列为空,返回 -1 。
int myCircularQueueFront(MyCircularQueue* obj) {
//队空直接返回-1
if(myCircularQueueIsEmpty(obj)){
return -1;
}
else{
return obj->a[obj->front];
}
}
Rear
: 获取队尾元素。如果队列为空,返回 -1 。
int myCircularQueueRear(MyCircularQueue* obj) {
//队空直接返回-1
if(myCircularQueueIsEmpty(obj)){
return -1;
}
else{
return obj->a[(obj->rear-1+obj->k)%(obj->k)];
}
}
Free
:销毁
void myCircularQueueFree(MyCircularQueue* obj) {
free(obj->a);
obj->a=NULL;
free(obj);
obj=NULL;
}
以上就是关于循环队列的讲解,如果哪里有错的话,可以在评论区指正,也欢迎大家一起讨论学习,如果对你的学习有帮助的话,点点赞关注支持一下吧!!!