栈和队列——设计循环队列

思考

1.如何实现

循环队列首先需要思考的是,基于什么来实现。那么我们可以想到数组和链表。对于链表,单链表呢,我们不方便找到上一个节点,需要遍历整个链表去找到,且他的时间复杂度为O(N), 所以,单双链表我们均不考虑。那么就只剩下数组了。对于数组,我们可以创建两个变量head和tail用来存储头节点和尾节点,所以访问上一个节点我们就可以直接通过-1来访问了。

2.数组的满与空冲突问题

我们选择用数组去实现这个问题,假设我们共有四个空间,当我们试验后发现,在这个数组中数组中元素满的情况为head=tail,数组为空的情况仍为head=tail。这时就会出现假溢出的情况,对于这种情况我们可以有两种解决方法。

方法一:我们可以在结构体中增加一个元素size用来存储当前数组中的元素个数,当size=0时数组为空,当size=k时,数组为满。

方法二:多创建一个空间,使空间总数为k+1,这样,当tail+1=head时证明数组为满,当tail=head时证明数组为空。

我们选择方法二来解答这道题。

1.结构体创建

typedef struct 
{
    int*a;
    int head;//数组的第一个元素
    int tail;//数组的尾元素的下一个元素
    int k;
} MyCircularQueue;

2.结构体的初始化

首先,我们需要先开辟一块空间来存储结构体,然后再开辟一块空间存储数组,因为我们的数组中需要多开辟一个空间用来防止假溢出,所以数组中元素个数为k+1,所以开辟空间大小为int*(k+1)。再将head与tail等于零,k等于题中所给的k值。

MyCircularQueue* myCircularQueueCreate(int k) 
{
    MyCircularQueue*obj=(MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    obj->a=(int*)malloc(sizeof(int)*(k+1));//多创建出来一个空间防止假溢出
    obj->head=0;
    obj->tail=0;
    obj->k=k;
    return obj;
}

3.myCircularQueueIsEmpty()检查循环队列是否为空

如上文所说,判断循环队列为空的条件就是head=tail。

bool myCircularQueueIsEmpty(MyCircularQueue* obj) 
{
    return obj->tail==obj->head;
}

4.myCircularQueueIsFull()检查循环队列是否已满

如上文,判断循环队列是否已满的条件就是tail+1=head,但是,我们考虑一下极端情况。

                                                       head

1234

                                                                                              tail

此时,tail+1!=head,所以我们就需要借助取余符号,我们将所有tail+1都对k+1进行取余,当tail+1小于k+1时对于结果并没有影响,但是,当tail+1=5时,我们就可以应用取余的方法使tail+1=head,也就是完成了题目中要求的循环。

bool myCircularQueueIsFull(MyCircularQueue* obj) 
{
    return (obj->tail+1)%(obj->k+1)==obj->head;
}

5.boolmyCircularQueueEnQueue() 向循环队列插入一个元素

想要在循环队列中插入元素,首先我们需要调用myCircularQueueIsFull()函数,判断循环队列是否已满,如果满了,则返回false。如果还有空位,我们直接将元素赋值给tail,再++tail。但是如果是如图的特殊情况,我们则需要在下面将tail对k+1进行取余,这样就能使在尾部的tail重新回到头部。

bool myCircularQueueDeQueue(MyCircularQueue* obj) 
{
    if(myCircularQueueIsEmpty(obj))
    {
        return false;
    }
    ++obj->head;
    obj->head%=(obj->k+1);
    return true;
}

6.myCircularQueueFront()从队首获取元素

首先,我们需要调用myCircularQueueIsEmpty()函数判断这个循环队列是否为空队列,如果为空则返回-1,如果不为空队列则直接返回队列中下标为head的元素。

int myCircularQueueFront(MyCircularQueue* obj) 
{
    if(myCircularQueueIsEmpty(obj))
    {
        return -1;
    }
    return obj->a[obj->head];

}

7.myCircularQueueRear()获取队尾元素

首先,我们需要调用myCircularQueueIsEmpty()函数判断这个队列是否为空队列,如果为空则返回-1。由于tail是队尾元素的下一个元素,所以想要获取队尾元素,就需要将tail-1。那么如下图的特殊情况我们应当如何才能回到数组的尾呢。与上面的向循环队列插入元素一样,我们借助取余的方法,让tail-1再加k+1,再对k+1进行取余,这样,对小于k+1的数没有任何影响,且当tail=0时,可以让tail成功的循环回去,访问到队尾元素。

                                                                           head

  123

                                                       tail

int myCircularQueueRear(MyCircularQueue* obj) 
{
    if(myCircularQueueIsEmpty(obj))
    {
        return -1;
    }
    return obj->a[((obj->tail-1)+(obj->k+1))%(obj->k+1)];
}

3. myCircularQueueFree()空间释放

想要释放空间,我们应当先从数组开始释放,数组释放完毕后再对结构体进行释放。

void myCircularQueueFree(MyCircularQueue* obj) 
{
    free(obj->a);
    free(obj);
}

大家有兴趣的可以自行尝试一下哦~

. - 力扣(LeetCode)

  • 15
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值