前言:我们知道队列底层并不适合用顺序结构进行实现,因为队列为尾插头删的操作,当进行头删时我们需要将后序元素进行往前搬移,时间复杂度便是O(N),这样出队的效率便大大降低了,故我们通常选用链表作为队列实现的底层结构。
问题提出:那若使用连续的存储空间时,出队列的操作就无法达到O(1)吗?
循环队列的提出
1.front为对头元素下标,back为队尾元素下标。当队列进行头删时,为使出队列的操作达到O(1),我们不进行元素的搬移,将对头front往后移动一步。
我们将1 2 3 4进行依次入队列,当对1进行头删操作后,我们将front进行往后移动一步,如下所示:
2.在1的操作上将5 6 7 8 9 0进行依次入队操作
我们可以看到,在对元素进行入队的时候,back一直往后移动,移动到空间末尾的时候,认为此队列已满,而空间起始位置是有剩余空间存在,我们将此种情况称为***队列假溢出问题。***
而若队列中有效元素已将空间彻底填充满,而后序还需要进行元素的入对操作,对队列的操作一直为入队,且在入队列过程中没有出队列造成空间为满,我们将此种情况称为***队列真溢出***。
3.循环队列能有效地解决队列空间假溢出问题。
循环队列
1.我们按照队列元素头删front向后移动,元素尾插back向后移动,初始队列为空时,front和back在初始位置0。
我们往空循环队列里插入a1,a2,a3,a4,a5,a6,a7,a8,每插入一个元素back指针往后移动一步,当队列空间被有效元素填满时,则我们可以看到,空队列的情况和队列为满的情况一样,那我们该怎么区分两种情况呢?
2.一般循环队列空间大小固定,以N为循环队列的空间容纳元素个数。
方式一:少用一个存储空间。
队列为空:front=back
队列为满:(back+1)%N=front
方式二:使用标记flag。
flag初始值设置为0,当元素进行入队操作时,flag设置为1,当元素进行出队操作时,flag设置为0。
队列为空:front=back && flag=1
队列为满:front=back && flag=0
方式三:使用计数count。
当元素进行入队操作时,count进行加1操作。
当元素进行出队操作时,count进行减1操作。
队列为空:count=0
队列为满:count=N
循环队列相关操作
此处我们以OJ题来进行循环队列的相应操作展示。链接如下:https://leetcode-cn.com/problems/design-circular-queue/
我们定义循环队列结构如下:
typedef struct
{
//循环队列一般空间大小固定
int*array;
int capacity;//空间大小
int size;//队列有效元素个数,主要用来判断队列是空还是满
int front;//对头
int back;//队尾
} MyCircularQueue;
1.MyCircularQueue(k): 构造器,设置队列长度为 k 。
MyCircularQueue* myCircularQueueCreate(int k)
{
MyCircularQueue* mq=(MyCircularQueue*)malloc(sizeof(MyCircularQueue));
if(mq==NULL)
{
assert(0);
return NULL;
}
mq->array=(int*)malloc(sizeof(int)*k);
mq->capacity=k;
mq->size=0;
mq->front=0;
mq->back=0;
return mq;
}
2.Front: 从队首获取元素。如果队列为空,返回 -1 。
//获取对头元素,队列为空返回-1
int myCircularQueueFront(MyCircularQueue* obj)
{
if(myCircularQueueIsEmpty(obj))
{
return -1;
}
return obj->array[obj->front];
}
3.Rear: 获取队尾元素。如果队列为空,返回 -1 。
//获取队尾元素,队列为空返回-1
int myCircularQueueRear(MyCircularQueue* obj)
{
if(myCircularQueueIsEmpty(obj))
{
return -1;
}
return obj->array[(obj->back-1+obj->capacity)%obj->capacity];
}
4.enQueue(value): 向循环队列插入一个元素。如果成功插入则返回真。
//向循环队列插入元素,成功插入则返回真
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value)
{
if(myCircularQueueIsFull(obj))//队列满则无法插入数值
{
return false;
}
//元素存储到back的位置上
obj->array[obj->back]=value;
obj->back++;
//插入元素后可能造成队列为满,则得将back放置到队列的起始位置
if(obj->back==obj->capacity)
obj->back=0;
obj->size++;
return true;
}
5.deQueue(): 从循环队列中删除一个元素。如果成功删除则返回真。
//删除元素,成功删除返回真
bool myCircularQueueDeQueue(MyCircularQueue* obj)
{
//队列判空,为空则无法删除元素
if(myCircularQueueIsEmpty(obj))
{
return false;
}
//队列不为空,则进行队列元素删除操作
obj->front++;
//front移动到队列末尾时,将front置为空
if(obj->front==obj->capacity)
{
obj->front=0;
}
obj->size--;
return true;
}
6.isEmpty(): 检查循环队列是否为空。
bool myCircularQueueIsEmpty(MyCircularQueue* obj)
{
return obj->size==0;
}
7.isFull(): 检查循环队列是否已满。
bool myCircularQueueIsFull(MyCircularQueue* obj)
{
if(obj->size==obj->capacity)
{
return true;
}
return false;
}
8.Free();销毁队列
void myCircularQueueFree(MyCircularQueue* obj)
{
free(obj->array);
free(obj);
}
9.完整代码如下:
typedef struct
{
//循环队列一般空间大小固定
int*array;
int capacity;//空间大小
int size;//队列有效元素个数,主要用来判断队列是空还是满
int front;//对头
int back;//队尾
} MyCircularQueue;
MyCircularQueue* myCircularQueueCreate(int k)
{
MyCircularQueue* mq=(MyCircularQueue*)malloc(sizeof(MyCircularQueue));
if(mq==NULL)
{
assert(0);
return NULL;
}
mq->array=(int*)malloc(sizeof(int)*k);
mq->capacity=k;
mq->size=0;
mq->front=0;
mq->back=0;
return mq;
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj)
{
return obj->size==0;
}
bool myCircularQueueIsFull(MyCircularQueue* obj)
{
if(obj->size==obj->capacity)
{
return true;
}
return false;
}
//向循环队列插入元素,成功插入则返回真
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value)
{
if(myCircularQueueIsFull(obj))//队列满无法插入函数
{
return false;
}
//元素存储到back的位置上
obj->array[obj->back]=value;
obj->back++;
//插入元素后可能造成队列为满,则得将back放置到队列的起始位置
if(obj->back==obj->capacity)
obj->back=0;
obj->size++;
return true;
}
//删除元素,成功删除返回真
bool myCircularQueueDeQueue(MyCircularQueue* obj)
{
//队列判空,为空则无法删除元素
if(myCircularQueueIsEmpty(obj))
{
return false;
}
//队列不为空,则进行队列元素删除操作
obj->front++;
//front移动到队列末尾时,将front置为空
if(obj->front==obj->capacity)
{
obj->front=0;
}
obj->size--;
return true;
}
//获取对头元素,队列为空返回-1
int myCircularQueueFront(MyCircularQueue* obj)
{
if(myCircularQueueIsEmpty(obj))
{
return -1;
}
return obj->array[obj->front];
}
//获取队尾元素,队列为空返回-1
int myCircularQueueRear(MyCircularQueue* obj)
{
if(myCircularQueueIsEmpty(obj))
{
return -1;
}
return obj->array[(obj->back-1+obj->capacity)%obj->capacity];
}
void myCircularQueueFree(MyCircularQueue* obj)
{
free(obj->array);
free(obj);
}