设计循环队列

        这是leetcode的第622题。循环队列是一种线性数据结构,遵循先进先出原则,并且队尾被连接在队首之后以形成一个循环。现在的问题是要如何实现循环队列呢,是用链表还是用数组呢?

        我们可以一一探讨一下。首先,为了实现循环队列,我们必须知道不断插入数据时,队列什么时候为空,什么时候满了,是否能再插入数据。因此,不管是用链表实现,还是用数组实现,我们都需要有一个头指针和一个尾指针指向队列的头和尾,用来判断空和满。

        如果使用链表:最开始没有数据时,头指针front和尾指针rear指向同一个节点,front==rear,队列为空。插入数据时,rear指针向后移动。当队列满时,front和rear指针也指向同一个节点。所以这种方法不好判断队列的空和满的状态。

                

         这时,我们就想办法把一个节点空出来,当rear->next==front时,我们就认为它满了,这样就可以分辨出队列是空还是满。删除数据时,只要把front后移。这时,队列又可以插入数据,rear后移。但是,用链表实现循环队列有一个很大的弊端,就是不能快速的访问到尾节点。

        

         因为链表实现有弊端,所以我们尝试使用数组来解决。为了分辨空和满,我们也留一个空不不填。这样还能轻松取到数组最后一个插入的数据。所以这里采用数组要更加灵活。

        使用数组,这里的重点是要怎样实现循环,不想链表可以直接访问next,数组只要是以下标来控制。当在数组最后一个位置插入数据时,rear++,rear会指向数组外,但我们想要的是rear再次指向下标为0的节点,实现循环。所以这时为了让rear指向0,可以让rear=rear%(k+1)。这里大家可以结合图和代码仔细体会。

     

         因为需要多个数据来控制循环队列的实现,所以,我们定义一个结构体,里面包含数组、头指针、尾指针和记录循环队列空间大小的k。

typedef struct {
    int* a;//数组指针,因为不知道队列里究竟有多少数据,方便开辟空间
    int front;//头指针
    int rear;//尾指针
    int k;//记录队列空间大小
} MyCircularQueue;

        循环队列的基本操作包括向循环队列插入一个元素、从循环队列中删除一个元素、检查循环队列是否为空、检查循环队列是否已满、获取队头元素和获取队尾元素等。

        在使用结构体之前,要先给它初始化,避免野指针和随机值问题。

MyCircularQueue* myCircularQueueCreate(int k) 
{
    //为结构体开辟空间
    MyCircularQueue* obj=(MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    if(obj==NULL)//开辟失败,报错返回
    {
        perror("malloc");
        return NULL;
    }
    //为数组开辟空间
    obj->a=(int*)malloc(sizeof(int)*(k+1));//多开辟一个空间,因为有一个空间要空出来,用于分辨空和满
    if(obj->a==NULL)
    {
        perror("malloc");
        return NULL;
    }
    obj->front=obj->rear=0;//初始时,头指针和尾指针都指向下标为0的空间
    obj->k=k;//空间大小
    return obj;
}

         向循环队列插入一个元素。插入前判断队列是否已满,满了就不能插入。要注意rear的值。

bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) 
{
    assert(obj);//obj不能为空
    if(myCircularQueueIsFull(obj))//判满
        return false;
    else
    {
        obj->a[obj->rear]=value;
        obj->rear++;//尾指针往后走一个
        //如果在数组最后插入,rear会走到数组外面,造成越界
        if(obj->rear>=(obj->k+1))//如果rear等于所开辟的空间大小,就说明越界了
            obj->rear=obj->rear%(obj->k+1);//取模运算结果的特点是不会大于除数
    }
    return true;
}

   

         从循环队列中删除一个元素。front指针后移就行,里面的值有没有删掉或者被其他值覆盖并不重要,因为不会被访问到。删除前要判断队列是否为空,为空就不能再删除。

bool myCircularQueueDeQueue(MyCircularQueue* obj) 
{
    assert(obj);
    if(myCircularQueueIsEmpty(obj))//判空
        return false;
    else
    {
        obj->front++;
        //front一直++,总会有越界的时候,这时也要作和rear同样的判断
        if(obj->front>=(obj->k+1))
            obj->front=obj->front%(obj->k+1);
    }
    return true;
}

        检查循环队列是否为空。循环队列为空只有一种情况,就是头指针和尾指针指向同一块地方。

bool myCircularQueueIsEmpty(MyCircularQueue* obj) 
{
    assert(obj);
    return obj->front==obj->rear;
}

        检查循环队列是否已满。当rear的下一个位置是front时,队列满。

bool myCircularQueueIsFull(MyCircularQueue* obj) 
{
    assert(obj);
    return (obj->rear+1)%(obj->k+1)==obj->front;
}

        获取队头元素。返回front所这向的元素就行。

int myCircularQueueFront(MyCircularQueue* obj) 
{
    assert(obj);
    if(myCircularQueueIsEmpty(obj))//队列为空时,单独处理
        return -1;
    else
    {
        return obj->a[obj->front];
    }
}

        获取队尾元素。返回rear的前一个单元的数据。但是,取尾要比取头麻烦一点。因为当rear指向的是头节点时,rear = 0;rear-1 = -1,也会越界。所以也要取模一下。

int myCircularQueueRear(MyCircularQueue* obj) 
{
    assert(obj);
    if(myCircularQueueIsEmpty(obj))//队列为空时,单独处理
        return -1;
    else
    {
        //rear-1=-1时,先加上k+1,在和k+1取模
        return obj->a[(obj->rear-1+obj->k+1)%(obj->k+1)];
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值