• 栈和队列(2)


    目录

    🍁一、队列的概念

    🍁二、针对本文章给出的几点注意事项:

    🍁三、队列的存储结构

    🌕(一)、队列的顺序存储结构

    ⭐️ 循环队列的介绍:

    ⭐️ 循环队列的入队操作:

    ⭐️ 循环队列的出队操作:

    ⭐️ 判断队满的约定:

    🌕(二)、队列的链式存储

    🍁四、队列的实现

    🌕(一)、代码定义

    注意:

    🌕(二)、初始化

    🌕(三)、入队

    🌕(四)、出队

    🌕(五)、取队头元素

    🌕(六)、取队尾元素

    🌕(七)、判空

    🌕(八)、获取队列元素个数

    🌕(九)、销毁

    🌕(十)、遍历

    🍁、队列实现源代码

    🌕(一)、main.c

    🌕(二)、Queue.c

    🌕(三)、Queue.h




    🍁一、队列的概念

    ①:队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表。
    ②:队列具有 先进先出(简称FIFO (First In First Out))的特点
    ③:入队列:进行插入操作的一端称为 队尾
           出队列:进行删除操作的一端称为 队头
    ④:常见使用场景:

    🍁二、针对本文章给出的几点注意事项:

    (一)、队列使用单向队列即可,因为双向体现不了它的优势,而且双向还会多一个指针域,内存能省一点是一点

    (二)、哨兵位(带头)可要可不要,因为哨兵一般解决结构体二级指针,而本次小编会以另外一种方法解决二级指针的问题

    (三)、队列可以用数组实现,也可以用链表来实现,但优先选择链表,对于队列,链表的优势远大于数组,因为数组入队很简单,但出队就涉及数据的覆盖,比较麻烦;

    (四)、在链表的头部进行出队,尾部进行入队。

    (五)、队列实现的具体步骤我会放在代码注释里面,大家请注意一下!因为小编找不到合适的方式,有推荐的小伙伴可以评论区给小编一些建议。

    🍁三、队列的存储结构

    🌕(一)、队列的顺序存储结构

    1.队列的顺序存储简称“顺序队列”;

    2.它由一个存放队列元素的一维数组,以及分别指示队头和队尾的“箭头”所组成;

    3.通常约定:队尾箭头指示队尾元素在一维数组的当前位置;队头箭头指示队头元素在一维数组中当前位置的前一个位置;

    4.代码定义:

    1. #define maxsize 10//队列可能的最大长度
    2. typedef int elemtype;
    3. typedef struct
    4. {
    5. elemtype elem[maxsize];
    6. int front;//队头箭头
    7. int rear;//队尾箭头
    8. }squeuetp;

    假设Sq为squeuetp变量,即Sq表示一个顺序队列,则入栈操作为:

    1. Sq.rear=Sq.rear+1;//修改队尾指针rear
    2. Sq.elem[Sq.rear]=x;//将数据x放入rear所指示的位置

    类似的出队列需要修改队头指针:

    Sq.front=Sq.front+1

    以下是出队的几种状态:

    由图可见,当队空时:Sq.front==Sq.rear,但是队满的条件是什么呐?

    队满条件会是:Sq.rear==maxsize吗?

    答:由第三幅图可以看出,显然不是,在此图状态下顺序队列的存储空间并没有被占满,因此这是一种“假溢出”的现象。

    ⭐️ 循环队列的介绍:

    为了克服假溢出的现象,一个巧妙的方法办法是把队列设想成为一个循环的表;

    设想Sq.elem[0]接在Sq.elem[maxsize-1]位置之后。

    这种存储结构称为“循环队列”

    此时,利用取余运算(%),很容易实现队头。队尾指针意义下的加1操作,

    ⭐️ 循环队列的入队操作:

    此时的入队操作变为:加1取余

    1. Sq.rear=(Sq.rear+1)%maxsize;
    2. Sq.elem[Sq.rear]=x;

    例如如果Sq.rear指向elem[maxsize-1]的位置,那么下一次就会指向

    elem[(maxsize-1+1)%maxsize]的位置,即elem[0]的位置;

    这样,我们就可以循环利用每个位置的空间,解决的假溢出问题;

    ⭐️ 循环队列的出队操作:

    出队操作变为:

    Sq.front=(Sq.front+1)%maxsize;
    ⭐️ 判断队满的约定:

    为了能准确确定队满的条件,我们约定队头指针指示的位置不用来存放元素,这样当队尾指针“绕一圈”后追上队头指针,视为队满,故此时

    队满的条件为

    (Sq.rear+1)%maxsize==Sq.front

    队空的条件为

    Sq.front==Sq.rear

    队满图:

    对空图:

    🌕(二)、队列的链式存储

    上面已经介绍过了,即用单链表拉实现队列;

    并且队列的链式存储比顺序存储优势大,所以下面我们实现也是实现队列的链式存储;

    🍁四、队列的实现

    🌕(一)、代码定义

    之前实现链表的时候我们知道,在不带头且插入第一个数据时会修改结构体指针,此时就需要使用二级指针的参与,从而分类讨论,是情况变得复杂;既然我们不带头,并且这里即需要头指针还需要尾指针,那么我们可以有如下实现:

    1. typedef int QDatatype;
    2. typedef struct QueueNode
    3. {
    4. struct QueueNode* next;
    5. QDatatype data;
    6. }QNode;
    7. typedef struct Queue
    8. {
    9. QNode* head;//头指针,指向首结点
    10. QNode* tail;//尾指针,指向为结点
    11. int size;//记录队列长度
    12. }Que;

    我们将头指针head和尾指针tail重新封装成一个结构体,这样即使是在插入第一个数据,那么我们修改head指针,相当于修改结构体Queue的成员,所以我们全部情况只需要结构体Queue的一级指针即可;

    注意:

    截至目前:我们已经接触了三种改变结构体指针head不需要传参数二级指针的方法:

    (一)、就是使用带哨兵的head,这样改变的是head的next域,即改变结构体成员;

    (二)、使用不带哨兵的head,但最后会返回新head,在函数外面用适当的指针来接收返回值;

    (三)、就是上述所示,在不带哨兵的情况,将头指针、尾指针封装成一个结构体,这样改变head或tail时,改变的是该结构体的成员,只需使用该结构体的一级指针即可;

    🌕(二)、初始化

    1. //初始化
    2. void Queueinit(Que* ps)
    3. {
    4. assert(ps);
    5. ps->head = ps->tail = NULL;
    6. ps->size = 0;
    7. }

    🌕(三)、入队

    ①:因为我们使用的是链表结构,所以不用考虑增容;

    ②:但入队的时候要注意第一次入队时的区别,从而分类讨论;

    ③:入队后元素数量加1;

    1. //入队
    2. void QueuePush(Que* ps, QDatatype x)
    3. {
    4. assert(ps);
    5. //创建一个新结点
    6. QNode* newnode = (QNode*)malloc(sizeof(QNode));
    7. if (newnode == NULL)
    8. {
    9. perror("malloc");
    10. return;
    11. }
    12. //因为是在尾结点入队,所以入队之后结点next域要置空
    13. newnode->data = x;
    14. newnode->next = NULL;
    15. //第一次插入是结构体指针之间的赋值,之后才是结构体成员的赋值,所以要分情况
    16. //记住tail指针始终指向尾结点,所以入队之后要对tail指针重定位
    17. if (ps->tail == NULL)
    18. {
    19. ps->head = ps->tail = newnode;
    20. }
    21. else
    22. {
    23. ps->tail->next = newnode;
    24. ps->tail = newnode;
    25. }
    26. //入队后元素数量+1
    27. ps->size++;
    28. }

    🌕(四)、出队

    出队的时候也要注意几点事项:

    ①:出队前,一定要检查队列是否为空;

    ②:因为队列涉及头指针head和尾指针tail,所以当出队列的最后一个元素后,head和tail指针都要指向NULL,所以我们可以分类讨论以防万一;

    ③:出队后元素数量size减1

    1. //出队
    2. void QueuePop(Que* ps)
    3. {
    4. assert(ps);
    5. //检查队列是否为空,若为空则assert函数报错提示
    6. assert(!QueueEmpty(ps));
    7. //队列不为空,进行尾删
    8. //当对列只剩下一个元素时,要注意head和tail指针都要指向NULL,所以为了安全起见,进行分类讨论
    9. if (ps->head->next == NULL)
    10. {
    11. free(ps->head);
    12. //注意free释放的是该指针指向的空间,而不是释掉该指针
    13. ps->head = ps->tail = NULL;
    14. }
    15. else
    16. {
    17. QNode* next = ps->head->next;
    18. free(ps->head);
    19. ps->head = next;
    20. }
    21. //出队列,元素数量-1
    22. ps->size--;
    23. }

    🌕(五)、取队头元素

    操作很简单,只需返回head结点的data域;

    但要注意,首先要检查队列是否为空:

    1. //取队头
    2. QDatatype QueueFront(Que* ps)
    3. {
    4. assert(ps);
    5. //检查队列为不为空
    6. assert(!QueueEmpty(ps));
    7. //返回首结点的data域
    8. return ps->head->data;
    9. }

    🌕(六)、取队尾元素

    操作和(五)类似:

    1. //取队尾
    2. QDatatype QueueBack(Que* ps)
    3. {
    4. assert(ps);
    5. //检查队列为不为空
    6. assert(!QueueEmpty(ps));
    7. //返回尾结点的data域
    8. return ps->tail->data;
    9. }

    🌕(七)、判空

    规则:

    队列为空返回真;

    队列不为空返回假;

    我们用一个表达式的逻辑值来实现,如下:

    1. //判空
    2. bool QueueEmpty(Que* ps)
    3. {
    4. assert(ps);
    5. //为空返回真,不为空返回假
    6. return (ps->head == NULL);
    7. }

    🌕(八)、获取队列元素个数

    操作很简单,直接返回size即可:

    1. //获取队列元素个数
    2. int QueueSize(Que* ps)
    3. {
    4. assert(ps);
    5. return ps->size;
    6. }

    🌕(九)、销毁

    与单链表的销毁类似,一个结点一个结点的销毁;

    先用临时指针cur指向head,再用临时指针next保存cur的下一个结点,再释放当前结点cur,然后cur重定位到下一个结点,直至cur为NULL;

    1. //销毁
    2. void QueueDestroy(Que* ps)
    3. {
    4. assert(ps);
    5. QNode* cur = ps->head;
    6. //先保存下一个结点,在释放当前结点,在重定位
    7. while (cur)
    8. {
    9. QNode* Qnext = cur->next;
    10. free(cur);
    11. cur = Qnext;
    12. }
    13. ps->head = ps->tail = NULL;
    14. ps->size = 0;
    15. }

    🌕(十)、遍历

    与栈类似的,队列遍历的时候也不用封装成函数,而是用循环,边遍历边出队,遍历完后,队列为空,这与其后面使用场景有着密切联系;

    1. void Queuetest1()
    2. {
    3. Que ps;
    4. Queueinit(&ps);
    5. QueuePush(&ps, 1);
    6. QueuePush(&ps, 2);
    7. QueuePush(&ps, 3);
    8. QueuePush(&ps, 4);
    9. QueuePush(&ps, 5);
    10. //遍历
    11. while (!QueueEmpty(&ps))
    12. {
    13. //当前数据是整形,所以占位符用%d
    14. printf("%d ", QueueFront(&ps));
    15. QueuePop(&ps);
    16. }
    17. printf("\n");
    18. QueueDestroy(&ps);
    19. }

    先进先出,符合其特征;

    🍁、队列实现源代码

    🌕(一)、main.c

    1. #include"Queue.h"
    2. void Queuetest1()
    3. {
    4. Que ps;
    5. Queueinit(&ps);
    6. QueuePush(&ps, 1);
    7. QueuePush(&ps, 2);
    8. QueuePush(&ps, 3);
    9. QueuePush(&ps, 4);
    10. QueuePush(&ps, 5);
    11. //遍历
    12. while (!QueueEmpty(&ps))
    13. {
    14. //当前数据是整形,所以占位符用%d
    15. printf("%d ", QueueFront(&ps));
    16. QueuePop(&ps);
    17. }
    18. printf("\n");
    19. QueueDestroy(&ps);
    20. }
    21. int main()
    22. {
    23. Queuetest1();
    24. return 0;
    25. }

    🌕(二)、Queue.c

    1. #include"Queue.h"
    2. //初始化
    3. void Queueinit(Que* ps)
    4. {
    5. assert(ps);
    6. ps->head = ps->tail = NULL;
    7. ps->size = 0;
    8. }
    9. //销毁
    10. void QueueDestroy(Que* ps)
    11. {
    12. assert(ps);
    13. QNode* cur = ps->head;
    14. //先保存下一个结点,在释放当前结点,在重定位
    15. while (cur)
    16. {
    17. QNode* Qnext = cur->next;
    18. free(cur);
    19. cur = Qnext;
    20. }
    21. ps->head = ps->tail = NULL;
    22. ps->size = 0;
    23. }
    24. //入队
    25. void QueuePush(Que* ps, QDatatype x)
    26. {
    27. assert(ps);
    28. //创建一个新结点
    29. QNode* newnode = (QNode*)malloc(sizeof(QNode));
    30. if (newnode == NULL)
    31. {
    32. perror("malloc");
    33. return;
    34. }
    35. //因为是在尾结点入队,所以入队之后结点next域要置空
    36. newnode->data = x;
    37. newnode->next = NULL;
    38. //第一次插入是结构体指针之间的赋值,之后才是结构体成员的赋值,所以要分情况
    39. //记住tail指针始终指向尾结点,所以入队之后要对tail指针重定位
    40. if (ps->tail == NULL)
    41. {
    42. ps->head = ps->tail = newnode;
    43. }
    44. else
    45. {
    46. ps->tail->next = newnode;
    47. ps->tail = newnode;
    48. }
    49. //入队后元素数量+1
    50. ps->size++;
    51. }
    52. //出队
    53. void QueuePop(Que* ps)
    54. {
    55. assert(ps);
    56. //检查队列是否为空,若为空则assert函数报错提示
    57. assert(!QueueEmpty(ps));
    58. //队列不为空,进行尾删
    59. //当对列只剩下一个元素时,要注意head和tail指针都要指向NULL,所以为了安全起见,进行分类讨论
    60. if (ps->head->next == NULL)
    61. {
    62. free(ps->head);
    63. //注意free释放的是该指针指向的空间,而不是释掉该指针
    64. ps->head = ps->tail = NULL;
    65. }
    66. else
    67. {
    68. QNode* next = ps->head->next;
    69. free(ps->head);
    70. ps->head = next;
    71. }
    72. //出队列,元素数量-1
    73. ps->size--;
    74. }
    75. //取队头
    76. QDatatype QueueFront(Que* ps)
    77. {
    78. assert(ps);
    79. //检查队列为不为空
    80. assert(!QueueEmpty(ps));
    81. //返回首结点的data域
    82. return ps->head->data;
    83. }
    84. //取队尾
    85. QDatatype QueueBack(Que* ps)
    86. {
    87. assert(ps);
    88. //检查队列为不为空
    89. assert(!QueueEmpty(ps));
    90. //返回尾结点的data域
    91. return ps->tail->data;
    92. }
    93. //判空
    94. bool QueueEmpty(Que* ps)
    95. {
    96. assert(ps);
    97. //为空返回真,不为空返回假
    98. return (ps->head == NULL);
    99. }
    100. //获取队列元素个数
    101. int QueueSize(Que* ps)
    102. {
    103. assert(ps);
    104. return ps->size;
    105. }

    🌕(三)、Queue.h

    1. #pragma once
    2. #define _CRT_SECURE_NO_WARNINGS 1
    3. #include
    4. #include
    5. #include
    6. #include
    7. typedef int QDatatype;
    8. typedef struct QueueNode
    9. {
    10. struct QueueNode* next;
    11. QDatatype data;
    12. }QNode;
    13. typedef struct Queue
    14. {
    15. QNode* head;//头指针,指向首结点
    16. QNode* tail;//尾指针,指向为结点
    17. int size;//记录队列长度
    18. }Que;
    19. //初始化
    20. void Queueinit(Que* ps);
    21. //销毁
    22. void QueueDestroy(Que* ps);
    23. //入队
    24. void QueuePush(Que* ps,QDatatype x);
    25. //出队
    26. void QueuePop(Que *ps);
    27. //取队头
    28. QDatatype QueueFront(Que* ps);
    29. //取队尾
    30. QDatatype QueueBack(Que* ps);
    31. //判空
    32. bool QueueEmpty(Que* ps);
    33. //获取队列元素个数
    34. int QueueSize(Que* ps);

    本次知识到此结束,希望对你有所帮助

  • 相关阅读:
    (附源码)mysql小程序+springboot流浪动物保护平台 毕业设计 161154
    记一次 .NET某网络边缘计算系统 卡死分析
    JS——初始DOM的相关操作练习
    kubeadm init失败
    Nacos下载与安装详解
    1776年美国才建国,那一年中国在干什么?
    Java学习--学生管理系统(残破版)
    SpringBoot自动装配 Spring相关 常用设计模式 双亲委派 MongoDB Redis 适配器模式与策略模式
    java精品旅游项目管理系统计算机毕业设计MyBatis+系统+LW文档+源码+调试部署
    prometheus+grafana进行服务器资源监控
  • 原文地址:https://blog.csdn.net/hffh123/article/details/134036165