当前位置:   article > 正文

【数据结构】队列(Queue)的实现 -- 详解_队列代码

队列代码

一、队列的概念及结构

1、概念

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


入队列:进行插入操作的一端称为队尾

出队列:进行删除操作的一端称为队头


2、结构


(1)队列的顺序存储结构

  • 入队,不需要移动任何元素,时间复杂度为 O(1)
  • 出队,所有元素需要往前移动,时间复杂度为 O(N)

(2)队列的链表存储结构

首先我们定义两个指针,队头指针指向第一个节点,队尾指针指向尾节点。

  • 入队(尾插),时间复杂度为 O(1)
  • 出队(头删),时间复杂度为 O(1)

 二、队列的实现

如果使用数组删除队头数据,再挪动数据效率为 O(N),不适合。 选择用单向链表来完成队列的实现更好,可以发现头删和尾插效率都很高,而双向链表在这里发挥不出很大的作用。


1、创建文件

  • test.c(主函数、测试队列各个接口功能)
  • Queue.c(队列接口函数的实现)
  • Queue.h(队列的类型定义、接口函数声明、引用的头文件)

2、Queue.h 头文件代码

  1. // Queue.h
  2. // 链式结构:表示队列
  3. #pragma once
  4. #include<stdio.h>
  5. #include<stdbool.h> //bool
  6. #include<assert.h> // assert
  7. #include<stdlib.h> // malloc
  8. typedef int QDataType;
  9. typedef struct QueueNode // 队列节点结构
  10. {
  11. struct QueueNode* next; // 节点指针
  12. QDataType data; // 节点数据
  13. }QueueNode;
  14. typedef struct Queue // 队列的链式结构
  15. {
  16. QueueNode* head; //队头指针
  17. QueueNode* tail; //队尾指针
  18. }Queue;
  19. // 初始化队列
  20. void QueueInit(Queue* pq);
  21. // 销毁队列
  22. void QueueDestroy(Queue* pq);
  23. // 队尾入队列
  24. void QueuePush(Queue* pq, QDataType data);
  25. // 队头出队列
  26. void QueuePop(Queue* pq);
  27. // 获取队列头部元素
  28. QDataType QueueFront(Queue* pq);
  29. // 获取队列队尾元素
  30. QDataType QueueBack(Queue* pq);
  31. // 获取队列中有效元素个数
  32. int QueueSize(Queue* pq);
  33. // 检测队列是否为空,如果为空返回非零结果,如果非空返回0
  34. bool QueueEmpty(Queue* pq);

三、Queue.c 中各个接口函数的实现

1、初始化队列

  1. // 初始化队列
  2. void QueueInit(Queue* pq)
  3. {
  4. assert(pq);
  5. pq->head = pq->tail = NULL;
  6. }

2、队列的销毁

  1. // 销毁队列
  2. void QueueDestroy(Queue* pq)
  3. {
  4. assert(pq);
  5. QueueNode* cur = pq->head;
  6. while (cur)
  7. {
  8. QueueNode* next = cur->next;
  9. free(cur);
  10. cur = next;
  11. }
  12. cur = NULL;
  13. pq->head = pq->tail = NULL;
  14. }

3、队尾入队列(尾插)

  1. // 队尾入队列
  2. void QueuePush(Queue* pq, QDataType x)
  3. {
  4. assert(pq);
  5. QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode)); // 动态申请一个节点
  6. newnode->data = x;
  7. newnode->next = NULL; // 尾节点next指针置空
  8. if (pq->head == NULL) // 队列为空
  9. {
  10. pq->head = pq->tail = newnode;
  11. }
  12. else // 队列不为空
  13. {
  14. pq->tail->next = newnode;
  15. pq->tail = newnode; // 更新队尾指针
  16. }
  17. }

4、队头出队列(头删)

  1. // 队头出队列
  2. // 写法一:
  3. void QueuePop(Queue* pq)
  4. {
  5. assert(pq);
  6. //温柔处理方式:
  7. /* if(pq->head == NULL)
  8. return; */
  9. //暴力处理方式:
  10. assert(!QueueEmpty(pq));
  11. QueueNode* next = pq->head->next; // 记录头节点的直接后继
  12. free(pq->head); // 释放头节点
  13. pq->head = next; // 更新队头指针
  14. if (pq->head == NULL) // 队列中只有一个节点
  15. {
  16. pq->tail = NULL;
  17. }
  18. }
  19. // 写法二:
  20. void QueuePop(Queue* pq)
  21. {
  22. assert(pq);
  23. //暴力处理方式:
  24. assert(!QueueEmpty(pq));
  25. if(pq->head->next == NULL) // 一个节点
  26. {
  27. free(pq->head);
  28. pq->head = pq->tail = NULL;
  29. }
  30. else // 多个节点
  31. {
  32. QueueNode* next = pq->head->next; // 记录头节点的直接后继
  33. free(pq->head); // 释放头节点
  34. pq->head = next; // 更新队头指针
  35. }
  36. }

注意:链表只有一个节点时,要单独处理,否则可能会造成 pq->tail 野指针的情况。 


5、获取队列头部元素

  1. // 获取队列头部元素
  2. QDataType QueueFront(Queue* pq)
  3. {
  4. assert(pq);
  5. assert(!QueueEmpty(pq));
  6. return pq->head->data;
  7. }

6、获取队列队尾元素

  1. // 获取队列队尾元素
  2. QDataType QueueBack(Queue* pq)
  3. {
  4. assert(pq);
  5. assert(!QueueEmpty(pq));
  6. return pq->tail->data;
  7. }

7、获取队列中有效元素个数

  1. // 获取队列中有效元素个数
  2. int QueueSize(Queue* pq)
  3. {
  4. assert(pq);
  5. int n = 0;
  6. QueueNode* cur = pq->head;
  7. while (cur)
  8. {
  9. n++;
  10. cur = cur->next;
  11. }
  12. return n;
  13. }

如果频繁调用这个接口函数,可以在 QueuePtr 中加一个 size 来记录数据的个数。 


8、检查队列是否为空

  1. // 检测队列是否为空,如果为空返回非零结果,如果非空返回0
  2. bool QueueEmpty(Queue* pq);
  3. {
  4. assert(pq);
  5. return pq->head == NULL && pq->tail == NULL;
  6. }

四、整合代码

  1. // Queue.c
  2. #include "Queue.h"
  3. // 初始化队列
  4. void QueueInit(Queue* pq)
  5. {
  6. assert(pq);
  7. pq->head = pq->tail = NULL;
  8. }
  9. // 销毁队列
  10. void QueueDestroy(Queue* pq)
  11. {
  12. assert(pq);
  13. QueueNode* cur = pq->head;
  14. while (cur)
  15. {
  16. QueueNode* next = cur->next;
  17. free(cur);
  18. cur = next;
  19. }
  20. cur = NULL;
  21. pq->head = pq->tail = NULL;
  22. }
  23. // 队尾入队列
  24. void QueuePush(Queue* pq, QDataType x)
  25. {
  26. assert(pq);
  27. QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode)); // 动态申请一个节点
  28. newnode->data = x;
  29. newnode->next = NULL; // 尾节点next指针置空
  30. if (pq->head == NULL) // 队列为空
  31. {
  32. pq->head = pq->tail = newnode;
  33. }
  34. else // 队列不为空
  35. {
  36. pq->tail->next = newnode;
  37. pq->tail = newnode; // 更新队尾指针
  38. }
  39. }
  40. // 队头出队列
  41. void QueuePop(Queue* pq)
  42. {
  43. assert(pq);
  44. assert(!QueueEmpty(pq));
  45. QueueNode* next = pq->head->next; // 记录头节点的直接后继
  46. free(pq->head); // 释放头节点
  47. pq->head = next; // 更新队头指针
  48. if (pq->head == NULL) // 队列中只有一个节点
  49. {
  50. pq->tail = NULL;
  51. }
  52. }
  53. // 获取队列头部元素
  54. QDataType QueueFront(Queue* pq)
  55. {
  56. assert(pq);
  57. assert(!QueueEmpty(pq));
  58. return pq->head->data;
  59. }
  60. // 获取队列队尾元素
  61. QDataType QueueBack(Queue* pq)
  62. {
  63. assert(pq);
  64. assert(!QueueEmpty(pq));
  65. return pq->tail->data;
  66. }
  67. // 获取队列中有效元素个数
  68. int QueueSize(Queue* pq)
  69. {
  70. assert(pq);
  71. int n = 0;
  72. QueueNode* cur = pq->head;
  73. while (cur)
  74. {
  75. n++;
  76. cur = cur->next;
  77. }
  78. return n;
  79. }
  80. // 检测队列是否为空,如果为空返回非零结果,如果非空返回0
  81. bool QueueEmpty(Queue* pq);
  82. {
  83. assert(pq);
  84. return pq->head == NULL && pq->tail == NULL;
  85. }

五、测试队列的功能



六、拓展 

实际中我们有时还会使用一种队列叫循环队列。环形队列可以使用数组实现,也可以使用循环链表实现。


1、空的循环队列


2、满的循环队列

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/706892
推荐阅读
相关标签
  

闽ICP备14008679号