LeetCode每日精进:622.设计循环队列
题目链接:622.设计循环队列
题目描述:
设计你的循环队列实现。 循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。
循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。
你的实现应该支持如下操作:
MyCircularQueue(k)
: 构造器,设置队列长度为 k 。Front
: 从队首获取元素。如果队列为空,返回 -1 。Rear
: 获取队尾元素。如果队列为空,返回 -1 。enQueue(value)
: 向循环队列插入一个元素。如果成功插入则返回真。deQueue()
: 从循环队列中删除一个元素。如果成功删除则返回真。isEmpty()
: 检查循环队列是否为空。isFull()
: 检查循环队列是否已满。
示例:
MyCircularQueue circularQueue = new MyCircularQueue(3); // 设置长度为 3 circularQueue.enQueue(1); // 返回 true circularQueue.enQueue(2); // 返回 true circularQueue.enQueue(3); // 返回 true circularQueue.enQueue(4); // 返回 false,队列已满 circularQueue.Rear(); // 返回 3 circularQueue.isFull(); // 返回 true circularQueue.deQueue(); // 返回 true circularQueue.enQueue(4); // 返回 true circularQueue.Rear(); // 返回 4
提示:
- 所有的值都在 0 至 1000 的范围内;
- 操作数将在 1 至 1000 的范围内;
- 请不要使用内置的队列库。
思路:
循环队列的逻辑结构
以队列长度k为4示例:
循环队列的底层结构
以队列长度k为4示例:
若用链表实现,那么就得使用环形链表实现循环结构
对于用链表实现的循环队列,除了需要手动连环之外,与队列的实现相同,还需要定义两个结构体, 参考队列:数据结构中的“排队艺术”,一个用来定义链表的结点的结构,一个用来定义队头和队尾的结构。
若用数组实现
对于用数组实现的循环队列,只需让队尾rear越界后模上数组容量即可让rear再次回到队头,从而实现循环结构。
分析:这样对比来看,用数组实现循环队列只需定义一个结构体,更加便捷。
循环队列的结构
以循环队列长度为4示例:
如图可以看到,当队列为空时,队头front和队尾rear指向同一个位置,当队列已满时,队头front和队尾rear依然指向同一个位置,所以单单是这样通过队列长度申请对应数据个数的数组是无法区分当前队列是空队列状态还是队列已满的状态,所以需要重新设计数组结构。
当循环队列长度为k时,我们申请k+1个数据空间的数组:
当队列为空,队头front和队尾rear指向同一个位置,即满足:
front == rear
当队列已满时,rear的下一个位置指向front,即满足:
( rear + 1 ) % ( k + 1 ) == front
那么这种结构就可以解决区分空队列和满队列的问题了。
typedef struct {
int* arr;
int front;
int rear;
int capacity;
} MyCircularQueue;
用capacity来记录队列当前的容量,front和rear分别指向队头和队尾。
创建循环队列
MyCircularQueue* myCircularQueueCreate(int k)
首先,创建循环队列的结构体,申请k+1个数据空间的数组,将队头与队尾指向数组头部,并将队列容量设置为k。
代码实现;
MyCircularQueue* myCircularQueueCreate(int k) {
MyCircularQueue* pq = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
pq->arr = (int*)malloc(sizeof(int)*(k+1));
pq->front = pq->rear = 0;
pq->capacity = k;
return pq;
}
检查循环队列是否为空
当队头和队尾指向同一位置时,循环队列为空,队列为空,返回true,队列不为空,返回false。
代码实现:
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
return obj->front == obj->rear;
}
检查循环队列是否已满
当队列已满时,rear的下一个位置指向front,满足上文推理的表达式:
( rear + 1 ) % ( k + 1 ) == front
队列已满,返回true,队列未满,返回false。
代码实现:
bool myCircularQueueIsFull(MyCircularQueue* obj) {
return (obj->rear+1)%(obj->capacity+1) == obj->front;
}
向循环队列插入一个元素
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value)
if (myCircularQueueIsFull(obj))
{
return false;
}
在插入元素之前,我们需要对检查队列是否已满,若队列已满,则直接返回false。
obj->arr[obj->rear++] = value;
若队列未满,则将元素添加到队尾位置,并让rear++。
obj->rear %= obj->capacity+1;
为防止出现rear++后rear越界的情况,需要再将rear模上数组长度,使rear回到数组头部,使其下一个位置为front。
插入成功,返回true。
完整代码:
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
if (myCircularQueueIsFull(obj))
{
return false;
}
obj->arr[obj->rear++] = value;
obj->rear %= obj->capacity+1;
return true;
}
从循环队列中删除一个元素
bool myCircularQueueDeQueue(MyCircularQueue* obj)
if (myCircularQueueIsEmpty(obj))
{
return false;
}
在删除元素前,需要判断队列是否为空,若队列为空。返回false。
obj->front++;
若队列不为空,front++,队头移向后一个位置。
obj->front %= obj->capacity+1;
为防止front++后,front越界,让front也模上数组长度,让其重新回到数组头部。
删除成功,返回true。
完整代码:
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
if (myCircularQueueIsEmpty(obj))
{
return false;
}
obj->front++;
obj->front %= obj->capacity+1;
return true;
}
从队首获取元素
int myCircularQueueFront(MyCircularQueue* obj) {
if (myCircularQueueIsEmpty(obj))
{
return -1;
}
return obj->arr[obj->front];
}
检查队列是否为空,若队列为空,则返回-1,若队列不为空,返回数组中front下标对应的元素。
获取队尾元素
int myCircularQueueRear(MyCircularQueue* obj)
if (myCircularQueueIsEmpty(obj))
{
return -1;
}
先检查队列是否为空,若队列为空,获取失败,则直接返回-1。
int prev = obj->rear-1;
若队列不为空,rear不位于数组头部,返回rear的前一个下标prev所储存的数据。
if (obj->rear == 0)
{
prev = obj->capacity;
}
若rear位于数组头部时,capacity所指向的数据就是队尾元素,返回即可。
完整代码:
int myCircularQueueRear(MyCircularQueue* obj) {
if (myCircularQueueIsEmpty(obj))
{
return -1;
}
int prev = obj->rear-1;
if (obj->rear == 0)
{
prev = obj->capacity;
}
return obj->arr[prev];
}
销毁
void myCircularQueueFree(MyCircularQueue* obj) {
free(obj->arr);
obj->arr = NULL;
free(obj);
obj = NULL;
}
销毁动态申请的数组后,由于参数obj接收了myCircularQueueCreate的返回值,所以也需要为其释放空间,置空。
代码总览:
typedef struct {
int* arr;
int front;
int rear;
int capacity;
} MyCircularQueue;
MyCircularQueue* myCircularQueueCreate(int k) {
MyCircularQueue* pq = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
pq->arr = (int*)malloc(sizeof(int)*(k+1));
pq->front = pq->rear = 0;
pq->capacity = k;
return pq;
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
return obj->front == obj->rear;
}
bool myCircularQueueIsFull(MyCircularQueue* obj) {
return (obj->rear+1)%(obj->capacity+1) == obj->front;
}
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
if (myCircularQueueIsFull(obj))
{
return false;
}
obj->arr[obj->rear++] = value;
obj->rear %= obj->capacity+1;
return true;
}
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
if (myCircularQueueIsEmpty(obj))
{
return false;
}
obj->front++;
obj->front %= obj->capacity+1;
return true;
}
int myCircularQueueFront(MyCircularQueue* obj) {
if (myCircularQueueIsEmpty(obj))
{
return -1;
}
return obj->arr[obj->front];
}
int myCircularQueueRear(MyCircularQueue* obj) {
if (myCircularQueueIsEmpty(obj))
{
return -1;
}
int prev = obj->rear-1;
if (obj->rear == 0)
{
prev = obj->capacity;
}
return obj->arr[prev];
}
void myCircularQueueFree(MyCircularQueue* obj) {
free(obj->arr);
obj->arr = NULL;
free(obj);
obj = NULL;
}