• 队列的基本操作(C语言实现)


    本篇博客带来栈的好兄弟——队列。

    目录

    一、 队列的概念与结构

    二、 队列的框架定义

    三、 队列的功能实现

    3.1 队列的初始化

    3.2 队列的销毁

    3.3 队列插入数据

    3.4 队列删除数据

    3.5 队列的判空

    3.6 取头部数据

    3.7 取尾部数据

    3.8 队列的大小

    四、功能测试

    五、总结


    一、 队列的概念与结构

    队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出(First In First Out)的特性。入队列:进行插入操作的一端称之为队尾;出队列:进行删除操作的一段称之为队头。

     

    二、 队列的框架定义

    队列也可以使用数组或链表实现,使用链表的结构实现更有一些,因为如果使用数组的结构,出队列在数组0下标处上出数据,效率会比较低。

    接下来我们来看看此次要实现的队列基本操作。

    1. #pragma once
    2. #include <stdio.h>
    3. #include <stdbool.h>
    4. #include <stdlib.h>
    5. #include <assert.h>
    6. typedef int QDataType;
    7. typedef struct QueueNode
    8. {
    9. struct QueueNode* next;
    10. QDataType data;
    11. }QNode;
    12. typedef struct Queue
    13. {
    14. QNode* head;
    15. QNode* tail;
    16. }Queue;
    17. void QueueInit(Queue* pq); //队列的初始化
    18. void QueueDestroy(Queue* pq); //队列的销毁
    19. void QueuePush(Queue* pq, QDataType x); //插入数据
    20. void QueuePop(Queue* pq); //删除数据
    21. bool QueueEmpty(Queue* pq); //队列的判空
    22. QDataType QueueFront(Queue* pq); //队列头部数据
    23. QDataType QueueBck(Queue* pq); //队列尾部数据
    24. int QueueSize(Queue* pq); //队列的大小

    注意:

    栈使用的是数组,数组是可以直接访问的。而如果我们使用链表实现队列,则要设置一个head和tail指针表示队头和对尾,其中head和tail都为普通的单链表节点结构,所以它们要设置next指针域和data数据域,这样才能实现队列的特性。

    三、 队列的功能实现

    3.1 队列的初始化

    和之前实现的单链表初始化差不多,这里就不过多讲解了。

    1. void QueueInit(Queue* pq)
    2. {
    3. assert(pq);
    4. pq->head = pq->tail = NULL;
    5. }

    3.2 队列的销毁

    因为是链表的形式,所以我们释放空间不能像数组一样直接整段释放,要使用while将每个节点释放掉。

    1. void QueueDestroy(Queue* pq)
    2. {
    3. assert(pq);
    4. QNode* Dest = pq->head;
    5. while (Dest)
    6. {
    7. QNode* temp = Dest->next;
    8. free(Dest);
    9. Dest = temp;
    10. }
    11. pq->head = pq->tail = NULL;
    12. }

    3.3 队列插入数据

    队列插入数据的步骤:

    将newnode链接到head节点的next;将newnode设置为新的tail节点。

    这里我们注意,在我们初始化的时候,我们将head和tail节点都置为了NULL,所以在我们第一次插入数据的时候要做一次判断,如果(head==NULL),则要将tail和head都设置为newnode节点,这样才能保证链表正常链接起来。

    注释写的很详细,大家可以看看。

    1. void QueuePush(Queue* pq, QDataType x)
    2. {
    3. assert(pq);
    4. //创建一个节点
    5. QNode* newnode = (QNode*)malloc(sizeof(QNode));
    6. if (newnode == NULL)
    7. {
    8. printf("malloc fail\n");
    9. exit(-1);
    10. }
    11. newnode->data = x;
    12. newnode->next = NULL;
    13. //判断pq是不是一个空结点
    14. //如果是 则将newnode置为当前队列的第一个结点
    15. //如果不是 则将newnode链接到pq的下一个位置
    16. if (pq->tail == NULL)
    17. { //两个节点都指向newnode 表示此时队列中就一个元素
    18. pq->head = pq->tail = newnode;
    19. }
    20. else
    21. {
    22. //1.将newnode链接到tail的后面
    23. pq->tail->next = newnode;
    24. //2.将newnode置换为新的tail
    25. pq->tail = newnode;
    26. }
    27. }

    3.4 队列删除数据

    上文就提到过,队列是先进先出,所以我们删除数据要从队头删除

    队列删除数据的步骤:

    保存head下一个节点的位置

    释放head节点

    将保存的节点设置为新的head。

    注意:

            ①要先判断队列不能为空

            ②如果队列中仅剩一个节点的时候,我们按以上的步骤释放了head节点并将其置为了NULL,但是我们没有处理tail节点,此时tail节点就成了经典的野指针。所以我们要额外判断当节点仅剩一个的时候,即(pq->head->next==NULL),我们要释放head结点,并将其head和tail都置为NULL。

    代码如下:

    1. void QueuePop(Queue* pq)
    2. {
    3. assert(pq);
    4. assert(!QueueEmpty(pq));
    5. //额外判断一下,解决如果仅剩一个结点了,pop的话导致tail为野指针
    6. if (pq->head->next == NULL)
    7. {
    8. free(pq->head);
    9. pq->head = pq->tail = NULL;
    10. }
    11. else
    12. {
    13. QNode* next = pq->head->next;
    14. free(pq->head);
    15. pq->head = next;
    16. }
    17. }

    3.5 队列的判空

    队列如何判空呢?

    此时我们只用判断head是否为NULL即可,即(head->next==NULL);

    1. bool QueueEmpty(Queue* pq)
    2. {
    3. assert(pq);
    4. return pq->head == NULL;
    5. }

    3.6 取头部数据

    注意: 这些取数据和删除数据的操作都要先判断队列是否为空。

    1. QDataType QueueFront(Queue* pq)
    2. {
    3. assert(pq);
    4. assert(!QueueEmpty(pq));
    5. return pq->head->data;
    6. }

    3.7 取尾部数据

    1. QDataType QueueBck(Queue* pq)
    2. {
    3. assert(pq);
    4. assert(!QueueEmpty(pq));
    5. return pq->tail ->data;
    6. }

    3.8 队列的大小

    链表不数组可以通过计算下标来算长度,所以这里我们只能使用循环计算直到cur==NULL;

    1. int QueueSize(Queue *pq)
    2. {
    3. assert(pq);
    4. int count = 0;
    5. QNode* cur = pq->head;
    6. while (cur)
    7. {
    8. cur = cur->next;
    9. count++;
    10. }
    11. return count;
    12. }

    四、功能测试

    还是和栈一样的测试方式来检查我们队列实现是否成功。

    同样,队列是不能遍历的,所以我们也要采用取出一个队列头部数据,然后将头部数据弹出,直到队列为空。

     

    五、总结

    相比于栈,队列的性质与栈的截然不同,同样我们这里使用的链式结构与数组的形式也不同。

    我感觉实现队列唯一的难点可能就是其中两层结构体的灵活使用吧,其他的与栈的实现很类似,大家可以把队列的框架定义看看,然后自己实现一下,这对于掌握结构体很有帮助,对队列的认识也会加深。

    好了,本篇博客到此就结束了,接下来做几道题目之后就会开始树的学习,一段全新的征程,大家一起学习数据结构,一起加油。

  • 相关阅读:
    LeetCode_前缀和_滑动窗口_中等_930.和相同的二元子数组
    【晚风摇叶之其他】抖音直播弹幕解析,连接websocket解析弹幕内容
    MySQL
    Spring事务、设计模式以及SpringBoot自动装配原理
    李宏毅深度学习self-attentin学习笔记
    对于BI可视化分析平台,你了解多少?
    书籍推荐:ChatGPT,大模型的预训练、迁移和中间件编程学习。
    【前端学习】—函数节流(九)
    c语言-浅谈指针(3)
    jar包打包成exe安装包
  • 原文地址:https://blog.csdn.net/Brant_zero/article/details/125495655