目录
一、什么是循环队列
循环队列是一种数据结构,它与普通队列类似,但是在循环队列中,当队列满时,新的元素可以覆盖队列前面的元素。循环队列通常使用数组来实现,在队列尾部插入元素,在队列头部删除元素,并且可以循环利用数组空间,使得队列的操作更加高效。循环队列可以解决普通队列需要频繁的元素搬移的问题,提高了队列的操作效率。
LeetCode原题链接:. 622.- 力扣(LeetCode)
二、链表实现循环队列
2.1 单链表实现
通过增加了一个limit变量实现,仅仅实现了逻辑上的循环队列,没有实现物理结构上的循环,该方法即使可以通过LeetCode判题,但不是推荐解法。
typedef struct {
struct ListNode *head;
struct ListNode *tail;
int limit; //限制容量
int size; //实际容量
} MyCircularQueue;
MyCircularQueue* myCircularQueueCreate(int k)
{
MyCircularQueue *obj = (MyCircularQueue *)malloc(sizeof(MyCircularQueue));
obj->limit = k;
obj->size = 0;
obj->head = obj->tail = NULL;
return obj;
}
//入队
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value)
{
if (obj->size >= obj->capacity)
{
return false;
}
struct ListNode *node = (struct ListNode *)malloc(sizeof(struct ListNode));
if(node==NULL)
{
perror("malloc");
exit(1);
}
node->val = value;
node->next = NULL;
//头插
if (!obj->head)
{
obj->head = obj->tail = node;
}
else
{
obj->tail->next = node;
obj->tail = node;
}
obj->size++;
return true;
}
//出队
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
if (obj->size == 0) {
return false;
}
struct ListNode *node = obj->head;
obj->head = obj->head->next;
obj->size--;
free(node);
return true;
}
//返回队首元素
int myCircularQueueFront(MyCircularQueue* obj) {
if (obj->size == 0) {
return -1;
}
return obj->head->val;
}
//返回队尾元素
int myCircularQueueRear(MyCircularQueue* obj) {
if (obj->size == 0) {
return -1;
}
return obj->tail->val;
}
//判空
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
return obj->size == 0;
}
//判满
bool myCircularQueueIsFull(MyCircularQueue* obj) {
return obj->size == obj->limit;
}
//销毁
void myCircularQueueFree(MyCircularQueue* obj) {
for (struct ListNode *temp = obj->head; temp;) {
struct ListNode *newnode = temp;
temp = temp->next;
free(newnode);
}
free(obj);
}
2.2 双链表实现
使用定长无头双向链表,实现循环队列
typedef struct ListNode
{
int data;
struct ListNode* next; // 指针保存下⼀个节点的地址
struct ListNode* prev; // 指针保存前⼀个节点的地址
}ListNode;
typedef struct {
ListNode* phead; //指向队首元素
ListNode* ptail; //指向队尾元素的下一个位置
int k;
int size;
} MyCircularQueue;
//重复开辟空间操作单独封装
ListNode* ListBuyNode(int x)
{
ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
if (newnode == NULL)
{
perror("malloc");
exit(1);
}
newnode->data = x;
newnode->next = newnode;
newnode->prev = newnode;
return newnode;
}
//尾插
void ListPushBack(ListNode* head)
{
assert(head);
ListNode* newnode = ListBuyNode(0);
newnode->prev = head->prev;
newnode->next = head;
head->prev->next = newnode;
head->prev = newnode;
}
//创建物理上的循环队列
MyCircularQueue* myCircularQueueCreate(int k)
{
MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
if (obj == NULL)
{
perror("malloc");
exit(1);
}
obj->phead = obj->ptail = ListBuyNode(0);
obj->size = 0;
obj->k = k;
while (k-1)
{
ListPushBack(obj->phead);
k--;
}
obj->ptail = obj->phead;
return obj;
}
//判空
bool myCircularQueueIsEmpty(MyCircularQueue* obj)
{
assert(obj);
return obj->size == 0;
}
//判满
bool myCircularQueueIsFull(MyCircularQueue* obj)
{
assert(obj);
return obj->size == obj->k;
}
//入队
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value)
{
assert(obj);
if (myCircularQueueIsFull(obj))
{
return false;
}
obj->ptail->data = value;
obj->ptail = obj->ptail->next;
obj->size++;
return true;
}
//出队
bool myCircularQueueDeQueue(MyCircularQueue* obj)
{
assert(obj);
if (myCircularQueueIsEmpty(obj))
{
return false;
}
obj->phead = obj->phead->next;
obj->size--;
return true;
}
//返回队首元素
int myCircularQueueFront(MyCircularQueue* obj)
{
assert(obj);
if (myCircularQueueIsEmpty(obj))
{
return -1;
}
return obj->phead->data;
}
//返回队尾元素
int myCircularQueueRear(MyCircularQueue* obj)
{
assert(obj);
if (myCircularQueueIsEmpty(obj))
{
return -1;
}
return obj->ptail->prev->data;
}
//销毁队列
void myCircularQueueFree(MyCircularQueue* obj)
{
assert(obj);
ListNode* temp = NULL;
while (obj->size)
{
temp = obj->phead;
obj->phead = obj->phead->next;
free(temp);
temp = NULL;
obj->size--;
}
obj->phead = obj->ptail = NULL;
free(obj);
}
三、数组实现循环队列
3.1 思路分析
3.1.1 如何判空判满
看到这个问题可能最直接的思路就是,当头指针与尾指针重合时,链表为满。但是通过图示不难发现,空和满的最后的结果都是一样的:头指针等于尾指针。
对于这个问题有以下两种解决方式:
- 在结构体中设置size变量:最直接且最有效的方式
- 额外开辟一个空间:将空和满的情况区分开,tail+1 = head,但是仍然涉及到了回环问题,见下文解释。
3.1.2 如何处理回环问题
tail指向最后一个元素,指向下一个元素时如何将tail的值变为从0开始?
- 若设计的空间等于有效空间个数:采用取模的做法。因为:任何小于被操作数的取模都等于它自身,等于被操作数的取模等于0。只需取空间的大小,便完美解决了该问题。
- 若设计的空间比有效空间的个数多一个:可以判断tail所指的元素是否为0,为0则tail置为0
同理:head也可以这样处理
3.1.3 如何取出尾部元素
如果是正常的情况(物理结构上头指针在尾指针的前面),无需考虑这个问题;主要考虑特殊情况(物理结构上头指针在尾指针的后面)。本质上讨论的依然是回环的问题。
- 既可以采用if判断tail是否为0
- 也可以采用取模的办法
3.2 开辟空间法实现
typedef struct {
int head; //指向首元素
int tail; //指向尾元素的下一个位置
int k;
int* a;
} MyCircularQueue;
MyCircularQueue* myCircularQueueCreate(int k)
{
MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
if (obj == NULL)
{
perror("malloc");
exit(1);
}
obj->a = (int*)malloc((k + 1) * sizeof(int));
if (obj->a == NULL)
{
perror("malloc");
exit(1);
}
obj->k = k;
obj->head = 0;
obj->tail = 0;
return obj;
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
return obj->head == obj->tail;
}
bool myCircularQueueIsFull(MyCircularQueue* obj) {
return (obj->tail + 1) % (obj->k + 1) == obj->head;
}
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
if (myCircularQueueIsFull(obj))
{
return false;
}
obj->a[obj->tail] = value;
obj->tail++;
//判断是否绕环
obj->tail %= (obj->k + 1);
return true;
}
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
if (myCircularQueueIsEmpty(obj))
{
return false;
}
obj->head++;
//判断是否绕环
obj->head %= (obj->k + 1);
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 - 1 + obj->k + 1) % (obj->k + 1)];
}
void myCircularQueueFree(MyCircularQueue* obj) {
free(obj->a);
free(obj);
}
3.3 创建size变量实现
typedef struct {
int head; //指向首元素
int tail; //指向尾元素的下一个位置
int k;
int* a;
int size;
} MyCircularQueue;
MyCircularQueue* myCircularQueueCreate(int k)
{
MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
if (obj == NULL)
{
perror("malloc");
exit(1);
}
obj->a = (int*)malloc(k * sizeof(int));
if (obj->a == NULL)
{
perror("malloc");
exit(1);
}
obj->k = k;
obj->head = 0;
obj->tail = 0;
obj->size = 0;
return obj;
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
return obj->size == 0;
}
bool myCircularQueueIsFull(MyCircularQueue* obj) {
return obj->size == obj->k;
}
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
if (myCircularQueueIsFull(obj))
{
return false;
}
obj->a[obj->tail] = value;
obj->tail++;
//判断是否绕环
obj->tail %= (obj->k);
return true;
}
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
if (myCircularQueueIsEmpty(obj))
{
return false;
}
obj->head++;
//判断是否绕环
obj->head %= (obj->k);
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) % (obj->k)];
}
void myCircularQueueFree(MyCircularQueue* obj) {
free(obj->a);
free(obj);
}