【数据结构】循环队列(C语言实现)

目录

一.定义

二.优点

三.实现

1.结构选择

2.判空判满

3.操作实现


一.定义

循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”

二.优点

我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。

三.实现

实现队列通常有两种方式:

顺序表

链表

实现普通队列时,一般都使用链表实现,其原因为顺序表实现时在进行出队列的操作时间复杂度为O(N)

1.结构选择

实现循环队列时,我们应该思考几个方面:

1.循环队列空间固定

2.CPU三级缓存命中

3.各种基本操作的时间复杂度

解释:

1.循环队列空间固定,数组实现仅需开辟一次固定的空间,链表则需要开辟n次空间后再链到一起。

2.数组空间连续,链表空间不连续,内存加载到CPU三级缓存都为一块块的数据块形式加载,故数组形式的缓存命中高。

3.循环队列的特性为,固定空间重复且循环利用,故无论链表还是数组其各个基本操作的时间复杂度均为O(1)

总结:

经过以上三点考虑,不难看出用数组实现循环队列最优。

//利用数组实现 
typedef struct 
{
    int* q;//队列空间
    int k;//有效存储个数
    int head;//头下标
    int tail;//尾下标
} MyCircularQueue;

2.判空判满

循环队列的最主要问题:判断队列为空,或者判断队列为满。

解决掉这两个问题,循环队列就变得非常容易了。

一开始head,tail指向下标为0的地方,此时队列为空,head == tail

每插入一个数据后,tail向后走一次,知道插入到最后一个位置tail继续向后走,由于是循环结构,tail再次指向head指向的位置,此时队列为满,head == tail

不难看出,判空判满这两个操作无法实现。

那么如何解决呢?

 

... ...

解决方法

方法一:在结构体内多加一个size来记录数据个数,size == 0就是空,size == n就满了(n是有效数据个数)

方法二:在第一个有效数据之前多出一个哨兵位,该哨兵位只起到占位作用,不存储有效数据。

方法一过于简单就不用图来说明了,重点来看下方法二:

如果++tail == head就说明满了。如果tail == head就说明为空。

注意:按照上面的例子tail+1 = 6,但这是循环队列,下标为6的地方就是下标为0的地方,不要混淆!

3.操作实现

创建循环队列,有效数据个数为k个。

//创建循环队列
MyCircularQueue* myCircularQueueCreate(int k) 
{
    MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    obj->q = (int*)malloc(sizeof(int)*(k+1));//开辟比有效数据多1个的空间
    obj->k = k;
    obj->head = obj->tail = 0;
    return obj;
}

入队列,注意数组下标的控制

//入队列
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) 
{
    //如果队列已满,则无法插入
    if(myCircularQueueIsFull(obj))
        return false;
    //插入数据
    obj->q[obj->tail] = value;
    obj->tail++;
    //循环下标判定,如果触碰边界,手动修正
    if(obj->tail == obj->k + 1)
    {
        obj->tail = 0;
    }
    //插入数据也可写成模运算的方式:obj->tail = (obj->tail+1)%(k+1)
    return true;
}

出队列,注意数组下标的控制

出队列时只需要让head++,让要删除的数据无效即可,等到后续新入数据覆盖无效数据

//出队列
bool myCircularQueueDeQueue(MyCircularQueue* obj) 
{
    //如果队列为空,则无法删除
    if(myCircularQueueIsEmpty(obj))
        return false;
    //删除数据
    obj->head++;
    if(obj->head == obj->k+1)
    {
        obj->head = 0;
    }
    //此处也可以写成模运算的方式:obj->head = (obj->head+1)%(k+1)
    return true;
}

取队头数据

//取队头数据
int myCircularQueueFront(MyCircularQueue* obj) 
{
    //循环队列为空,默认返回-1
    if(myCircularQueueIsEmpty(obj))
    {
        return -1;
    }
    return obj->q[obj->head];
}

取队尾数据,注意取队头时head是此时队头数据下标,而tail永远是队尾数据下标的下一个位置,所以要减一

//取队尾数据
int myCircularQueueRear(MyCircularQueue* obj) 
{
    //如果队列为空,默认返回-1
    if(myCircularQueueIsEmpty(obj))
    {
        return -1;
    }
    int tail = obj->tail;
    //如果触碰边界说明该位置需要循环,则手动修正
    if(tail - 1 < 0)
    {
        tail = obj->k + 1;
    }
    tail--;
    //此处也可以写成模运算的方式 tail = (tail-1 + k+1) % (k+1)
    return obj->q[tail];
}

销毁队列

//销毁队列
void myCircularQueueFree(MyCircularQueue* obj) 
{
    //注意释放的先后顺序
    free(obj->q);
    obj->q = NULL;
    free(obj);
    obj = NULL;
}

以上为循环队列的全部内容!

​​​​​​创作不易,如果感觉有帮助的话,可否支持下作者点个赞呢( •̀ ω •́ )y

  • 19
    点赞
  • 113
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值