设计循环队列

前言:队列中有一种特殊的存在——环形队列,其有一定的价值与意义,这篇文章主要由一道与其相关的例题来引出相关的知识内容。

注:下述解题过程是用C语言实现。

目录

一:题目简述 

二:环形队列的简单介绍

三:环形队列的实现

(1) 数组实现

1. 过程分析

2. 代码实现

(2) 链表实现

1.过程分析

2. 代码实现


一:题目简述 

 这是来自leetcode中一道与队列相关的题,题目难度中等,这道题的实质是要实现满足循环队列的两个条件,要避免进入队列是循环状态的误区。

大家可以先自己做一做:力扣 622.设计循环队列

二:环形队列的简单介绍

队列中有一种特殊的结构——循环队列,在一些场景会进行使用,有一定的价值。

环形队列的主要特定有两个:

1.  与队列一样,数据遵循先进先出的原则——所以只能从队尾插入数据。

2.  环形队列的空间大小是一定的,也就是说它的空间可以重复利用(用数组实现时)

注意:图示只是为了能够更直观的展现一种循环队列的感觉(且仅表示数组实现),实际上数组和链表实现起来依旧是用它们自已的的物理结构,只是通过条件控制满足了循环队列的条件,从而达到了设计循环队列的效果。


三:环形队列的实现

(1) 数组实现

由循环队列的特点我们知道循环队列的实现需要判断队列是否满了,而队列为空的判断条件是队头=队尾。用数组实现环形队列时,为了防止二者条件的重复,我们需要多开辟一个不属于队列的空间来表示队尾的位置(队尾数据的下一个),而这就是数组实现循环队列的关键之处。所以数组实现循环队列的要点就是当数组越界时再返回到数组的起始位置,即需要利用取模的思想控制数组的下标从而控制数组的边界,防止数组越界并实现循环的效果。

1. 过程分析

这里再强调一下:数组一定要多开辟一个空间来满足队列为满的条件的判断。

下面再分析几个特殊的边界情况:

(1) 队列为空时,head==tail

 (2) 队列为满时,tail的下一个位置是head

数组中判断环形队列为满的条件是比较重要的一项,为了防止数组越界并恰当的判断队列为满,当(tail+1)%(数组空间大小)==head即可。

(3) tail位于数组尾部,继续插入数据 (队头删除数据head走到尾部时情况类似,也要防止越界)

(4) 获取队尾数据(红圈表示队尾数据)时,有两种情况:

特殊:当tail位于数组首位时,tail的前一位就不能表示队尾数据的下标

正常:tail不位于数组首位,tail的前一位的下标就可以表示队尾数据的下标

解决方法也有两种:直接分情况或利用取模思想获取队尾下标(下述代码中可见)。

2. 代码实现

//1. 用数组实现 (要点:利用取模的思想控制边界)
typedef struct
{
    int* a;//动态开辟数组空间
    int head;
    int tail;//head与tail都表示数组的下标
    int capacity;//表示队列的实际容量(可以表示数组最后一个位置的下标)
} MyCircularQueue;

bool myCircularQueueIsEmpty(MyCircularQueue* obj);
bool myCircularQueueIsFull(MyCircularQueue* obj);//声明

MyCircularQueue* myCircularQueueCreate(int k)//设置队列实际容量为k
{
    MyCircularQueue* cq = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    cq->a = (int*)malloc(sizeof(int) * (k + 1));//数组要多开辟一个空间,使区分出队列空与满两种情况
    cq->head = cq->tail = 0;
    cq->capacity = k;
    return cq;
}

bool myCircularQueueEnQueue(MyCircularQueue* obj, int value)//插入数据
{
    if (myCircularQueueIsFull(obj))
    {
        return false;
    }
    else
    {
        obj->a[obj->tail] = value;
        obj->tail++;
        obj->tail %= (obj->capacity + 1);//向队尾插入数据时防止越界
        return true;
    }
}

bool myCircularQueueDeQueue(MyCircularQueue* obj)//删除数据
{
    if (myCircularQueueIsEmpty(obj))
    {
        return false;
    }
    else
    {
        obj->head++;
        obj->head %= (obj->capacity + 1);//防止越界
        return true;
    }
}

int myCircularQueueFront(MyCircularQueue* obj)//获取对头数据
{
    if (myCircularQueueIsEmpty(obj))
    {
        return -1;
    }
    else
    {
        return obj->a[obj->head];
    }
}

int myCircularQueueRear(MyCircularQueue* obj)//获取队尾数据
{
    if (myCircularQueueIsEmpty(obj))
    {
        return -1;
    }
    //else   解决1(更直观)
    //{
    //   if(obj->tail == 0)   当tail位于数组的起始位置时,tail-1不是队尾的值
    //    {
    //        return obj->a[obj->capacity];//直接在数组最后一个位置插入数据
    //    }
    //    else
    //    {
    //        return obj->a[obj->tail-1];
    //    }
    //}
    else     //解决2(利用取模的思想)
    {
        //我自己实现时犯的一个错误
        //obj->tail = (obj->tail+obj->capacity) % (obj->capacity+1);
        //return obj->a[obj->tail]; err (这里只是要获取队尾数据,不能改变tail的位置)

        int i = (obj->tail + obj->capacity) % (obj->capacity + 1);
        return obj->a[i];
    }
}

bool myCircularQueueIsEmpty(MyCircularQueue* obj)//判断队列是否为空
{
    return obj->head == obj->tail;
}

bool myCircularQueueIsFull(MyCircularQueue* obj)//判断队列是否为满
{
    return (obj->tail + 1) % (obj->capacity + 1) == obj->head;
}

void myCircularQueueFree(MyCircularQueue* obj)//销毁队列
{
    free(obj->a);
    free(obj);
}

(2) 链表实现

1.过程分析

用链表实现循环队列实际上比数组简单一些,与数组不同的是我们可以通过计数的方式来判断队列是否为满,这样就不用开辟额外的空间。

计数方式主要表现在定义链表结构时可以多定义一个size变量来表示链表中的有效数据个数,当其等于设置好的队列空间大小时,就可以表示循环队列已满,而队列满了后也可以通过出列(删除队头元素)使size减小进而能够再次向队列中插入数据。

注意:由于链表存在循环结构,所以可能有些人会认为链表实现循环队列是通过循环链表来实现的,我这里特别说明一下,也许可以(我不知道如何实现),但是我这里实现是通过普通单链表尾插头删控制队列内元素个数的多少来实现的(与实现队列的思路相同)。并且我认为这种实现方式已经足够简单了。

2. 代码实现

//2.用单链表实现循环队列————可以想象成一个大小固定并且可以移动的队列
//用单链表实现循环队列时,可以用size与capacity来控制队列空间,当size<capacity时就可以插入数据
//中心思想:空间大小固定,队列满时不可再入数据,但是可以通过删除队头数据使队列空间不再是满的状态从而能够继续向队列内入数据,从而达到了一种循环队列的效果
//要注意:链表不是环状,只是达到了循环队列的条件!!!!!
typedef struct
{
    struct ListNode* head;
    struct ListNode* tail;
//设置size与capacity,以二者大小判断队列空间,从而判断能否插入数据(通过设置计数的方式可以判断队列空或者满)
    int size;//有效数据个数
    int capacity;//链表容量(也表示队列的容量)
} MyCircularQueue;

bool myCircularQueueIsEmpty(MyCircularQueue* obj);
bool myCircularQueueIsFull(MyCircularQueue* obj);//两个函数的声明

MyCircularQueue* myCircularQueueCreate(int k)//创建队列
{
    MyCircularQueue* cq = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));//队列
    cq->head = cq->tail = NULL;
    cq->size = 0;
    cq->capacity = k;//给定队列容量为k
    return cq;
}

bool myCircularQueueEnQueue(MyCircularQueue* obj, int value)//入列
{
    if (myCircularQueueIsFull(obj))//队列满了就不可再入数据
    {
        return false;
    }
    else
    {
        struct ListNode* newnode = (struct ListNode*)malloc(sizeof(struct ListNode));
        newnode->val = value;
        newnode->next = NULL;
        if (obj->head == NULL)
        {
            obj->head = obj->tail = newnode;
        }
        else
        {
            obj->tail->next = newnode;
            obj->tail = newnode;
        }
        obj->size++;
        return true;
    }
}

bool myCircularQueueDeQueue(MyCircularQueue* obj)//出列
{
    if (myCircularQueueIsEmpty(obj))
    {
        return false;
    }
    else
    {
        struct ListNode* next = obj->head->next;
        free(obj->head);
        obj->size--;
        obj->head = next;
        return true;
    }
}

int myCircularQueueFront(MyCircularQueue* obj)//获取队头数据
{
    if (myCircularQueueIsEmpty(obj))
    {
        return -1;
    }
    return obj->head->val;
}

int myCircularQueueRear(MyCircularQueue* obj)//获取队尾数据
{
    if (myCircularQueueIsEmpty(obj))
    {
        return -1;
    }
    return obj->tail->val;
}

bool myCircularQueueIsEmpty(MyCircularQueue* obj)//判空
{
    return obj->size == 0;
}

bool myCircularQueueIsFull(MyCircularQueue* obj)//判满
{
    return obj->size == obj->capacity;
}

void myCircularQueueFree(MyCircularQueue* obj)//释放队列(相当于释放单链表)
{
    struct ListNode* cur = obj->head;
    while (cur)
    {
        struct ListNode* next = cur->next;
        free(cur);
        cur = next;
    }
    free(obj);
    obj = NULL;
}

总结:

设计循环队列主要是基于这道题目,我个人认为链表实现起来更为简单,但是大家可以都掌握。如各位发现了文章的错误,还望指正,谢谢。就这样,这次的分享到这里结束,再次希望各位能有所收获,再见。

  • 8
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
以下是使用C语言实现循环队列生成杨辉三角的代码,适合作为数据结构课程设计的实现: ```c #include <stdio.h> #include <stdlib.h> #define MAX_SIZE 100 // 定义循环队列 typedef struct { int front; // 队首指针 int rear; // 队尾指针 int items[MAX_SIZE]; // 队列元素 } Queue; // 初始化队列 void init_queue(Queue *q) { q->front = q->rear = 0; } // 判断队列是否为空 int is_empty(Queue *q) { return q->front == q->rear; } // 判断队列是否已满 int is_full(Queue *q) { return (q->rear + 1) % MAX_SIZE == q->front; } // 元素入队 void enqueue(Queue *q, int item) { if (!is_full(q)) { q->items[q->rear] = item; q->rear = (q->rear + 1) % MAX_SIZE; } } // 元素出队 int dequeue(Queue *q) { if (!is_empty(q)) { int item = q->items[q->front]; q->front = (q->front + 1) % MAX_SIZE; return item; } return -1; } // 生成杨辉三角 void generate_yanghui_triangle(int n) { Queue q; init_queue(&q); enqueue(&q, 1); // 第一行只有一个数字1 for (int i = 0; i < n; i++) { for (int j = 0; j <= i; j++) { printf("%d ", q.items[j]); } printf("\n"); for (int j = 0; j <= i; j++) { enqueue(&q, dequeue(&q) + q.items[j]); } enqueue(&q, 1); // 行末添加数字1 } } int main() { int n; printf("请输入杨辉三角的行数:"); scanf("%d", &n); generate_yanghui_triangle(n); // 生成n行杨辉三角 return 0; } ``` 在主函数中,通过scanf函数获取用户输入的杨辉三角的行数n,并将n作为参数传递给generate_yanghui_triangle函数。这样就可以根据用户输入的行数生成相应的杨辉三角。代码中还可以添加一些错误处理机制,例如判断用户输入的行数是否超出范围等等。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值