目录
一、循环队列的概念
实际中我们有时还会使用一种队列叫循环队列。循环队列中我们开辟一定的空间,用front和back两个指针指向头和尾,通过front和back的移动指示队列的入和出。
二、循环队列的实现
循环队列可以用数组去实现,先构建一个结构体其中包含一个维护动态开辟空间的指针和两个整形,一个保存头节点的坐标,另一个保存尾节点的坐标。完成后我们就可以讲解各个函数的使用。
1.Circular Queue.h
内部包含需要的头文件和需要的变量定义与函数声明
#include<stdio.h>
#include<stdbool.h>
#include<stdlib.h>
#include<assert.h>
#define TYPE int//保证队列数据的普适性
#define VOLUME 4//循环队列也需要空间,空间的大小是固定的,这个值就是我们能最多储存的值的多少
typedef struct CircularQueue
{
TYPE* arr;//动态维护数组的指针
int front;//头的位置下表
int back;//尾的位置下标
}CQ;
//构造器,设置队列长度为k
void circularqueue_Create(CQ* p);
//向循环队列插入一个元素
void circularqueue_push(CQ* obj, TYPE value);
//从循环队列删除一个元素
void circularqueue_pop(CQ* obj);
//从队首获取元素。
TYPE circularqueue_top(CQ* obj);
//获取队尾元素。
TYPE myCircularQueueRear(CQ* obj);
//检查循环队列是否为空。
bool IsEmpty(CQ* obj);
//检查循环队列是否已满。
bool IsFull(CQ* obj);
//清空
void QueueFree(CQ* obj);
2.函数讲解
(1)构造器
void circularqueue_Create(CQ* p);
细心的你可能会看到,我们malloc出来的数组足够放置 VOLUME+1 个数据,这是为了便于判断循环队列是否已满做出的空间牺牲,至于原因下面会讲。然后头尾坐标都为空,准备插入数据。
//构造器,设置队列长度为VOLUME
void circularqueue_Create(CQ* p)
{
p->arr = (TYPE*)malloc((VOLUME + 1)*sizeof(TYPE));
p->front = 0;
p->back = 0;
}
(2)判断队列是否为空
bool IsEmpty(CQ* obj);
很简单,back和front指示头尾坐标,头尾坐标相同时就没有有效数据了。
//检查循环队列是否为空。
bool IsEmpty(CQ* obj)
{
if (obj->front == obj->back)
return true;
else
return false;
}
(3)判断队列是否已满
bool IsFull(CQ* obj);
首先,我们需要明确的是循环队列内的back永远指向应该插入数据的位置。比如下标为0的地方插入数据,back就会变为为1;下标为1的地方插入数据,back就会变为为2等等,直到走到队列的最后一个数据存放位,插入完成后back又会回到头位置。
那么这时候问题就出现了:
队列为空和队列为满的时候,front和vack指向同一个下标,那么怎么去判断满和空呢?
此时,我们那个多开辟的空间就排上用场了,如果我们放弃一个位置,当数据储存满的时候,让back往后再走一步(注意,在删除数据后front就不再是数组的头节点,若为尾就跳到头)看看它是否和front指向的位置相同,相同就满了,不相同则不满。
//检查循环队列是否已满。
bool IsFull(CQ* obj)
{
int i = (obj->back + VOLUME + 2) % (VOLUME+1);
if (i != obj->front)
return false;
else
return true;
}
//0 1 2 3 4
//5 6 7 8 9
//1 2 3 4 0
(4)插入一个元素
void circularqueue_push(CQ* obj, TYPE value);
首先用assert断言是否为满,满了就不再插入。和顺序表一样的插入,不过为了保证插入到最后,我们不能只让back++,而是通过下面的公式得到新的下标:
back = (back + VOLUME + 2) % (VOLUME + 1)
//向循环队列插入一个元素
void circularqueue_push(CQ* obj, TYPE value)
{
assert(!IsFull(obj));
obj->arr[obj->back] = value;
obj->back = (obj->back + VOLUME + 2) % (VOLUME + 1);
//0 1 2 3 4
//6 7 8 9 10
//1 2 3 4 0
}
(5)删除一个元素
void circularqueue_pop(CQ* obj);
首先用assert断言是否为空,空了就不再删除。和顺序表一样的删除,不过为了保证循环,我们不能只让front++,而是依旧通过下面的公式得到新的下标:
front = (front + VOLUME + 2) % (VOLUME + 1)
//从循环队列删除一个元素
void circularqueue_pop(CQ* obj)
{
assert(!IsEmpty(obj));
obj->front = (obj->front + VOLUME + 2) % (VOLUME + 1);
//0 1 2 3 4
//6 7 8 9 10
//1 2 3 4 0
}
(6)获取队首元素
void circularqueue_pop(CQ* obj);
先判断为不为空,不为空直接返回首元素
//从队首获取元素。
TYPE circularqueue_top(CQ* obj)
{
assert(!IsEmpty(obj));
return obj->arr[obj->front];
}
(7)获取队尾元素
TYPE circularqueue_rear(CQ* obj);
先判断是否为空,此时的back就不能直接使用了,需要向前挪一位,在队首还需要挪到队尾,通过下面的公式实现:
i = (back + VOLUME) % (VOLUME + 1)
//获取队尾元素。
TYPE circularqueue_rear(CQ* obj)
{
assert(!IsEmpty(obj));
int i = (obj->back + VOLUME) % (VOLUME + 1);
return obj->arr[i];
//0 1 2 3 4
//4 5 6 7 8
//4 0 1 2 3
}
(8)清空数据
void QueueFree(CQ* obj);
不需要管内部的数据,直接让头尾下标回到原位置。如果想要销毁队列,在下面加一个free(obj->arr)就行了
//清空
void QueueFree(CQ* obj)
{
obj->back = 0;
obj->front = 0;
}
//销毁
void destory(CQ* obj)
{
obj->back = 0;
obj->front = 0;
free(obj->arr);
}
3.测试
tesr.c
void test()
{
CQ s;
CQ* p = &s;
circularqueue_Create(p);
circularqueue_push(p, 1);//1
circularqueue_push(p, 2);//1 2
circularqueue_pop(p);//2
circularqueue_push(p, 3);//2 3
circularqueue_pop(p);//3
circularqueue_push(p, 4);//3 4
circularqueue_push(p, 5);//3 4 5
circularqueue_push(p, 6);//3 4 5 6
int a = circularqueue_rear(p);
printf("%d\n", a);
if (IsFull(p) == true)
printf("满了\n");
destory(p);
}
int main()
{
test();
return 0;
}