题目
设计你的循环队列实现。 循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。
循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。
你的实现应该支持如下操作:
MyCircularQueue(k): 构造器,设置队列长度为 k 。
Front: 从队首获取元素。如果队列为空,返回 -1 。
Rear: 获取队尾元素。如果队列为空,返回 -1 。
enQueue(value): 向循环队列插入一个元素。如果成功插入则返回真。
deQueue(): 从循环队列中删除一个元素。如果成功删除则返回真。
isEmpty(): 检查循环队列是否为空。
isFull(): 检查循环队列是否已满。
思路设计
队列有顺序存储结构和链式存储结构两种选择,循环队列就是使用数组这种顺序存储结构来实现的,链队列就是使用链表这种链式存储结构来实现的。本题要求设计循环链表,那就需要使用顺序存储结构来实现。
我们需要一个头标记、一个尾标记来记录队列头和尾,还要一个参数记录顺序存储内存长度,一个数据类型指针。
我设计的循环链表当尾标记和头标记相等时为空,尾标记大于头标记 1 时队列为满。那么判断为满的数学表达式就是:(尾标记 + 1) % queueSize == 头标记
为方便叙述,循环队列里的头尾标记用头尾指针代替
上代码
#include <stdio.h>
typedef int QElemType;
typedef struct
{
QElemType *pBase; // 链表起点
int front; // 头标记
int rear; //尾标记
int maxSize; // 队列长度
} MyCircularQueue;
/** Initialize your data structure here. Set the size of the queue to be k. */
MyCircularQueue *myCircularQueueCreate(int k)
{
MyCircularQueue *circularQueue = malloc(sizeof(MyCircularQueue));
if (!circularQueue)
{
return NULL;
}
circularQueue->pBase = (QElemType *)malloc(sizeof(QElemType) * (k+1)); // 尾指针所指内存不存数据,所以队列实际长度比K大1
if (!circularQueue->pBase)
{
return NULL;
}
circularQueue->maxSize = k+1;
circularQueue->front = 0; // 头尾指针指向队列第一个位置,此时为空
circularQueue->rear = 0;
return circularQueue;
}
/** Insert an element into the circular queue. Return true if the operation is successful. */
bool myCircularQueueEnQueue(MyCircularQueue *obj, int value)
{
if ((obj->rear + 1) % obj->maxSize == obj->front) // 判断队列是否满
{
return false;
}
obj->pBase[obj->rear] = value; // 给尾指针所指内存赋值
obj->rear = (obj->rear + 1) % obj->maxSize; // 更新尾指针
return true;
}
/** Delete an element from the circular queue. Return true if the operation is successful. */
bool myCircularQueueDeQueue(MyCircularQueue *obj)
{
if ((obj->rear == obj->front)) // 判单队列是否为空
{
return false;
}
obj->front = (obj->front + 1) % obj->maxSize; // 考虑头指针在内存尾部,再次出队后头指针移动到内存头部的情况
return true;
}
/** Get the front item from the queue. */
int myCircularQueueFront(MyCircularQueue *obj)
{
if ((obj->rear == obj->front))
{
return -1;
}
int frontItem = obj->pBase[obj->front];
return frontItem;
}
/** Get the last item from the queue. */
int myCircularQueueRear(MyCircularQueue *obj)
{
if ((obj->rear == obj->front))
{
return -1;
}
if(0==obj->rear&&obj->front>obj->rear){ // 这里要判断是因为尾指针不存数据,直接用数组下标去最后一个数据就会出现
return obj->pBase[obj->maxSize-1]; //obj->pBase[obj->rear - 1] 下标减一的情况,意味着rear必须大于0,不然数组越界
}
if (obj->rear > 0)
{
int tailItem = obj->pBase[obj->rear - 1];
return tailItem;
}
return -1;
}
/** Checks whether the circular queue is empty or not. */
bool myCircularQueueIsEmpty(MyCircularQueue *obj)
{
if ((obj->rear == obj->front))
{
return true;
}
else
{
return false;
}
}
/** Checks whether the circular queue is full or not. */
bool myCircularQueueIsFull(MyCircularQueue *obj)
{
if ((obj->rear + 1) % obj->maxSize == obj->front)
{
return true;
}
else
{
return false;
}
}
void myCircularQueueFree(MyCircularQueue *obj)
{
free(obj); //直接释放指针
}
队列的链式实现----链队
使用链表来实现队列时就不需要考虑队列满的情况了,一般来空间是足够的。
#include <stdio.h>
typedef int QElemType;
typedef struct QNode // 节点结构
{
QElemType data;
struct QNode *next;
} QNode, *QueuePtr;
typedef struct // 链表结构
{
QueuePtr front, rear; // 头尾指针
} LinkQueue;
/**
* 入队
*/
int EnQueueu(LinkQueue *Q, QElemType e)
{
QueuePtr s = (QueuePtr)malloc(sizeof(QNode));
if (!s) //存储分配失败
{
return -1;
}
s->data = e;
s->next = NULL; // 构造新节点
Q->rear->next = s; // 让新节点成为队尾指针的下一个节点
Q->rear = s; // 把新节点设置为队尾节点
return 1;
}
/**
* 出队
*/
int DeQueue(LinkQueue *Q, QElemType *e)
{
if (Q->front == Q->rear)
{
return -1;
}
QueuePtr p;
p = Q->front->next; // 暂存下要出队的节点
*e = p->data;
Q->front->next = p->next; //头指针指向新的队首节点
if (Q->rear == p) // 这里是判断只有一个节点的队列删除节点后,应该将头尾指针指向同一地址
{
Q->rear = Q->front;
}
free(p); // 释放被删除节点
return e;
}
两种实现的比较
从时间上,他们的基本操作都是O(1),不过循环队列是实现申请号确定的空间大小,使用期间不用释放,但对于链队列,每次申请和释放内存还是有一点时间开销。如果操作频繁,两者还是会有一点细微差异。
从空间上,循环队列必须有一个固定的空间大小,就会存在空间浪费的问题,但链队列就不存在这个问题。