找回密码
 立即注册
首页 业界区 科技 循环队列的接口

循环队列的接口

眺愤 8 小时前
目录


目录

  • 目录
  • 队列的概念

    • 假溢出问题

  • 循环队列的实现

    • 1.以数组为基础实现循环队列
    • 2.以链表为基础实现循环队列

  • 两个栈实现队列

队列的概念

队列(Queue)和栈类似,相同点是都属于线性结构,不同点是栈遵循“后进先出”原则,而队列遵循“先进先出”的原则,也被称为“FIFO”结构,就是“First Input First Output”。
数据结构中的队列的两端都允许操作,只不过要求数据只能从队列的一端插入(入队),从队列的另一端删除(出队),可以把队列理解为一根水管,水管有进水口和出水口。一般把允许数据插入的一端称为队尾(Tail或者Rear),一般把允许删除数据的一端称为队头队首(Head或者Front)。
和栈一样,队列也可以通过数组或者链表来实现,通过数组来实现的队列我们称之为“顺序队列”,通过链表实现的队列称之为“链式队列”
假溢出问题

如果使用数据实现简单的队列,当执行多次入队和出队之后,队列的尾指针 Rear 和头指针 Front 已经到达数组的末尾,即使队列中有许多空闲位置(前面的元素删除后就会有位置),也无法再进行入队操作。这种情况被称为 “假溢出”,也就是队列又是空的又是满的,这看起来是一个自相矛盾的事情却在队列里面出现了,如下图所示:
1.png

解决方案:

  • 在学习顺序表(数组)的时候,当顺序表(数组)中删除一个元素,那么这个位置之后的所有元素都需要往前移动一位,所以队列中也是同理,假如我们用数组来实现的话,当元素出队时,我们把所有元素都往前移,不过这样因为每次都需要搬移数据,导致入队的时间复杂度是O(n)
  • 可以把队列设置为循环队列,循环队列也被称为“环形缓冲区”,通过使队列的尾部与头部相连,形成一个闭环,可以使得出队后的空间得到重新利用,从而最大限度地使用数组空间,避免了空间的浪费,在移动数据的时候只需要移一次即可,所以时间复杂度就是O(1)
循环队列的实现

1.以数组为基础实现循环队列
  1. /**
  2. * @file name : Cirqueue.c
  3. * @brief     :  该程序以数组为基础实现循环队列元素的初始化、入队、出队、遍历,另外为了提高可移植性,所以循环队列中
  4. *               数据元素的类型为DataType_t,用户可以根据实际情况修改循环队列中元素的类型
  5. * @author    :  MINDSETT@163.com
  6. * @date      :  2025/7/5
  7. * @version   :  1.0
  8. * @note      :  以数组末尾浪费一个存储空间为代价来换取更简便的代码
  9. * CopyRight (c)  2025  MINDSETT@163.com  All Right Reserved
  10. */
  11. #include <stdbool.h>
  12. #include <stdlib.h>
  13. #include <stdio.h>
  14. //指定循环队列中的元素的数据类型,用户可根据需要进行修改
  15. typedef int DataType_t;
  16. //构造一个实现循环队列的各项参数(循环队列首地址+循环队列的容量+队尾下标+队首下标)的结构体
  17. typedef struct Circular_Queue
  18. {
  19.     DataType_t *  addr;    //循环队列首地址
  20.     unsigned int  size;      //循环队列的容量
  21.     int           rear;       //队尾下标
  22.     int           front;       //队首下标
  23. }Cirqueue_t;
  24. /**
  25. * @name    :  Cirqueue_Create
  26. * @brief   :  创建一个循环队列并进行初始化
  27. * @param   :  None
  28. * @retval  :  返回栈底地址
  29. * @date    :  2025/7/5
  30. * @version :  1.0
  31. * @note    :  None
  32. */
  33. Cirqueue_t *Cirqueue_Create(unsigned int size)
  34. {
  35.     //1.利用calloc对循环队列的管理结构体申请一个内存
  36.     Cirqueue_t *manager=(Cirqueue_t *)calloc(1,sizeof(Cirqueue_t));
  37.     if (NULL == manager){
  38.         perror("calloc memory for manager is failed\n");
  39.         exit(-1);
  40.     }
  41.     //2.对循环队列管理结构体的所有元素进行初始化
  42.     manager->addr=(DataType_t *)calloc(size,sizeof(DataType_t));
  43.     if (NULL == manager->addr){
  44.         perror("calloc memory for Stack is failed\n");
  45.         exit(-1);
  46.     }
  47.     manager->size=size;
  48.     manager->rear=0;
  49.     manager->front=0;
  50.     return manager;
  51. }
  52. /**
  53. * @name    :  Cirqueue_IsFull
  54. * @brief   :  判断循环队列是否已满
  55. * @param   :  
  56. *              @manager:需要判断的循环队列
  57. * @retval  :  已满返回true,未满返回false
  58. * @date    :  2025/7/5
  59. * @version :  1.0
  60. * @note    :  None
  61. */
  62. bool Cirqueue_IsFull(Cirqueue_t *manager)
  63. {
  64.     return ( ( (manager->rear+1)%manager->size )==manager->front )? true : false;
  65. }
  66. /**
  67. * @name    :  Cirqueue_IsEmpty
  68. * @brief   :  判断循环队列是否为空
  69. * @param   :  
  70. *              @manager:需要判断的循环队列
  71. * @retval  :  为空返回true,不为空返回false
  72. * @date    :  2025/7/5
  73. * @version :  1.0
  74. * @note    :  None
  75. */
  76. bool Cirqueue_IsEmpty(Cirqueue_t *manager)
  77. {
  78.     return (manager->rear==manager->front)? true : false;
  79. }
  80. /**
  81. * @name    :  Cirqueue_Enqueue
  82. * @brief   :  入队
  83. * @param   :  
  84. *              @manager:需要操作的循环队列
  85. *              @Data:需要入队的元素
  86. * @retval  :  已满返回true,未满返回false
  87. * @date    :  2025/7/5
  88. * @version :  1.0
  89. * @note    :  None
  90. */
  91. bool Cirqueue_Enqueue(Cirqueue_t *manager,DataType_t Data)
  92. {
  93.     //判断循环队列是否已满
  94.     if ( Cirqueue_IsFull(manager) ){
  95.         printf("Circular Queue is Full\n");
  96.         return false;
  97.     }
  98.     //如果循环队列有剩余空间,则把新元素插入循环队列的队尾
  99.     manager->addr[manager->rear]=Data;
  100.     //防止队尾下标越界
  101.     manager->rear=(manager->rear+1)%manager->size;
  102.     return true;
  103. }
  104. /**
  105. * @name    :  Cirqueue_Dequeue
  106. * @brief   :  出队
  107. * @param   :  
  108. *              @manager:需要操作的循环队列
  109. * @retval  :  成功返回出队的元素
  110. * @date    :  2025/7/5
  111. * @version :  1.0
  112. * @note    :  None
  113. */
  114. DataType_t Cirqueue_Dequeue(Cirqueue_t *manager)
  115. {   
  116.     //判断循环队列是否为空
  117.     if ( Cirqueue_IsEmpty(manager) ){
  118.         printf("Circular Queue is empty\n");
  119.         return false;
  120.     }
  121.     //定义临时变量存储要出队的元素
  122.     DataType_t temp=0;
  123.     //队首元素出队,队首下标+1
  124.     temp=manager->addr[manager->front];
  125.     //防止队首下标越界
  126.     manager->front=(manager->front+1)%manager->size;
  127.     return temp;
  128. }
  129. /**
  130. * @name    :  Cirqueue_Print
  131. * @brief   :  遍历
  132. * @param   :  
  133. *              @manager:需要操作的循环队列
  134. * @retval  :  成功返回true,失败返回false
  135. * @date    :  2025/7/5
  136. * @version :  1.0
  137. * @note    :  None
  138. */
  139. bool Cirqueue_Print(Cirqueue_t *manager)
  140. {
  141.     if (Cirqueue_IsEmpty(manager)) {
  142.         printf("Circular Queue is empty\n");
  143.         return false;
  144.     }
  145.     int i = manager->front;
  146.     printf("Queue: ");
  147.     while (i != manager->rear) {
  148.         printf("%d ", manager->addr[i]);
  149.         i = (i + 1) % manager->size;
  150.     }
  151.     printf("\n");
  152.     return true;
  153. }
复制代码
2.以链表为基础实现循环队列
  1. /**
  2. * @file name : CirLnList_Queue.c
  3. * @brief     :  该程序以链表为基础实现循环队列元素的初始化、入队、出队、遍历,另外为了提高可移植性,所以循环队列中
  4. *               数据元素的类型为DataType_t,用户可以根据实际情况修改循环队列中元素的类型
  5. * @author    :  MINDSETT@163.com
  6. * @date      :  2025/7/11
  7. * @version   :  1.0
  8. * @note      :  头结点不存储数据,仅用于管理队列
  9. * CopyRight (c)  2025  MINDSETT@163.com  All Right Reserved
  10. */
  11. #include <stdio.h>
  12. #include <stdlib.h>
  13. #include <stdbool.h>
  14. //指定循环队列中元素的数据类型,用户可根据需要进行修改
  15. typedef int DataType_t;
  16. //构造循环队列的结点,循环队列中所有结点的数据类型应该是相同的(数据域+指针域)
  17. typedef struct CircularLinkedList_Queue
  18. {
  19.     DataType_t                  data;   //结点的数据域
  20.     struct CircularLinkedList_Queue *   next;   //结点的指针域
  21. }CirLnList_Queue_t;
  22. /**
  23. * @name    :  Queue_Create
  24. * @brief   :  创建一个空循环队列,空循环队列应该有一个头节点,对循环队列进行初始化
  25. * @param   :  None
  26. * @retval  :  返回头结点的地址
  27. * @date    :  2025/7/11
  28. * @version :  1.0
  29. * @note    :  None
  30. */
  31. CirLnList_Queue_t *Queue_Create(void)
  32. {
  33.     //创建一个头结点并对头结点申请内存
  34.     CirLnList_Queue_t* Head=(CirLnList_Queue_t*)calloc(1,sizeof(CirLnList_Queue_t));
  35.     if (NULL==Head){
  36.         perror("calloc memory for Head is failed\n");
  37.         exit(-1);
  38.     }
  39.     //对头结点进行初始化,头结点不存储数据域,指针域指向自身,体现“循环”思想
  40.     Head->next=Head;
  41.     //将头结点的地址返回
  42.     return Head;
  43. }
  44. /**
  45. * @name    :  Queue_NewNode
  46. * @brief   :  创建一个新结点,并为新结点申请堆内存以及对新结点的数据域和指针域进行初始化
  47. * @param   :  
  48. *             @data:新结点的数据
  49. * @retval  :  返回新结点的地址
  50. * @date    :  2025/7/11
  51. * @version :  1.0
  52. * @note    :  None
  53. */
  54. CirLnList_Queue_t * Queue_NewNode(DataType_t data)
  55. {
  56.     //创建新结点,并未新结点申请堆内存
  57.     CirLnList_Queue_t * New=(CirLnList_Queue_t*)calloc(1,sizeof(CirLnList_Queue_t));
  58.      if (NULL==New){
  59.         perror("calloc memory for NewNode is failed\n");
  60.         return NULL;
  61.     }
  62.     //对新结点的数据域和指针域进行初始化
  63.     New->data=data;
  64.     New->next=NULL;
  65.     return New;
  66. }
  67. /**
  68. * @name    :  Queue_Enqueue
  69. * @brief   :  入队
  70. * @param   :  
  71. *             @Head:插入的循环队列
  72. *             @data:新结点的数据
  73. * @retval  :  成功返回true,失败返回false
  74. * @date    :  2025/7/11
  75. * @version :  1.0
  76. * @note    :  None
  77. */
  78. bool Queue_Enqueue(CirLnList_Queue_t * Head,DataType_t data)
  79. {
  80.     //创建新结点,并对新结点进行初始化
  81.     CirLnList_Queue_t* New=Queue_NewNode(data);
  82.     if (NULL==New){
  83.         perror("Create New node is failed\n");
  84.         return false;
  85.     }
  86.     //判断循环队列是否为空,如果为空,则将新结点直接插入
  87.     if (Head==Head->next){
  88.         Head->next=New;
  89.         New->next=New;//指针域指向自身,体现“循环”思想
  90.         return true;
  91.     }
  92.     //备份首结点的地址
  93.     CirLnList_Queue_t * tail=Head->next;
  94.     //找到尾结点
  95.     while( tail->next != Head->next ){
  96.         tail=tail->next;
  97.     }
  98.     //新结点指向首结点,旧尾结点指向新结点
  99.     New->next=tail->next;
  100.     tail->next=New;
  101.     return true;
  102. }
  103. /**
  104. * @name    :  Queue_Dequeue
  105. * @brief   :  出队
  106. * @param   :  
  107. *             @Head:循环队列的头结点
  108. * @retval  :  成功返回true,失败返回false
  109. * @date    :  2025/7/11
  110. * @version :  1.0
  111. * @note    :  None
  112. */
  113. bool Queue_Dequeue(CirLnList_Queue_t* Head)
  114. {
  115.     //备份首结点
  116.     CirLnList_Queue_t * first=Head->next;
  117.     CirLnList_Queue_t * tail=Head->next;
  118.     //判断传参错误或循环队列为空
  119.     if (NULL==Head || Head==Head->next){
  120.         perror("Linked list is empty\n");
  121.         return false;
  122.     }
  123.     //1.只有一个首结点
  124.     if (Head->next->next==Head->next){
  125.         Head->next=Head;
  126.         first->next=NULL;//首结点的指针域指向NULL,从循环队列中断开(避免野指针)
  127.         free(first);
  128.         return true;
  129.     }
  130.     //2.循环队列有多个结点
  131.     //找到尾结点
  132.     while( tail->next!=Head->next ){
  133.         tail=tail->next;
  134.     }
  135.     //尾结点指向首结点的直接后驱
  136.     tail->next=first->next;
  137.     //将头结点指针域指向首结点的直接后驱
  138.     Head->next=first->next;
  139.     //旧的首结点的指针域指向NULL,从循环队列中断开(避免野指针)
  140.     first->next=NULL;
  141.     //释放首结点占用的内存
  142.     free(first);
  143.     return true;
  144. }
  145. /**
  146. * @name    :  Queue_Print
  147. * @brief   :  遍历输出循环队列中各个结点的数据
  148. * @param   :  
  149. *             @Head:需要遍历的循环队列
  150. * @retval  :  成功返回true,失败返回false
  151. * @date    :  2025/7/11
  152. * @version :  1.0
  153. * @note    :  None
  154. */
  155. bool Queue_Print(CirLnList_Queue_t* Head)
  156. {
  157.     //判断传参错误或循环队列为空
  158.     if (NULL==Head || Head==Head->next){
  159.         perror("Current CircularLinkedList is empty\n");
  160.         return false;
  161.     }
  162.     //对循环队列的头节点的地址进行备份
  163.     CirLnList_Queue_t *curr=Head;
  164.     //循环遍历循环队列
  165.     while(curr->next){
  166.         curr=curr->next;
  167.         printf("Data=%d ",curr->data);
  168.         if( curr->next == Head->next){
  169.             break;
  170.         }
  171.     }
  172.     return true;
  173. }
复制代码
两个栈实现队列

2.png
  1. // 基本思想:利用栈s1和栈s2实现队列,栈的思想是“先进后出”,队列的思想是“先进先出”,可以选择把栈s1作为入队缓存,栈s2作为出队缓存
复制代码
  1. //代码实现:在栈的基础上使用两个栈实现循环队列的入队和出队
  2. /**
  3. * @file name : Stack_Queue.c
  4. * @brief     :  该程序以栈为基础实现循环队列元素的初始化、入队、出队、遍历,另外为了提高可移植性,所以循环队列中
  5. *               数据元素的类型为DataType_t,用户可以根据实际情况修改循环队列中元素的类型
  6. * @author    :  MINDSETT@163.com
  7. * @date      :  2025/7/5
  8. * @version   :  1.0
  9. * @note      :  None
  10. * CopyRight (c)  2025  MINDSETT@163.com  All Right Reserved
  11. */
  12. #include <stdbool.h>
  13. #include <stdlib.h>
  14. #include <stdio.h>
  15. #define maxSize 10
  16. //指定顺序栈中的元素的数据类型,用户可根据需要进行修改
  17. typedef int DataType_t;
  18. //构造一个实现顺序栈的各项参数(栈底地址+栈容量+栈顶元素的下标)的结构体
  19. typedef struct Sequence_Stack
  20. {
  21.     DataType_t *  bottom;    //栈底地址
  22.     unsigned int  size;      //栈容量
  23.     int           top;       //栈顶元素的下标
  24. }SeqStack_t;
  25. /**
  26. * @name    :  SeqStack_Create
  27. * @brief   :  创建一个顺序栈并进行初始化
  28. * @param   :  None
  29. * @retval  :  返回栈底地址
  30. * @date    :  2025/7/5
  31. * @version :  1.0
  32. * @note    :  None
  33. */
  34. SeqStack_t *SeqStack_Create(unsigned int size)
  35. {
  36.     //1.利用calloc对顺序栈的管理结构体申请一个内存
  37.     SeqStack_t *manager=(SeqStack_t *)calloc(1,sizeof(SeqStack_t));
  38.     if (NULL == manager){
  39.         perror("calloc memory for manager is failed\n");
  40.         exit(-1);
  41.     }
  42.     //2.对顺序栈管理结构体的所有元素进行初始化
  43.     manager->bottom=(DataType_t *)calloc(size,sizeof(DataType_t));
  44.     if (NULL == manager->bottom){
  45.         perror("calloc memory for Stack is failed\n");
  46.         exit(-1);
  47.     }
  48.     manager->size=size;
  49.     manager->top=-1;
  50.     return manager;
  51. }
  52. /**
  53. * @name    :  SeqStack_IsFull
  54. * @brief   :  判断顺序栈是否已满
  55. * @param   :  
  56. *              @manager:需要判断的顺序栈
  57. * @retval  :  已满返回true,未满返回false
  58. * @date    :  2025/7/5
  59. * @version :  1.0
  60. * @note    :  None
  61. */
  62. bool SeqStack_IsFull(SeqStack_t *manager)
  63. {
  64.     return (manager->top+1==manager->size)? true : false;
  65. }
  66. /**
  67. * @name    :  SeqStack_IsEmpty
  68. * @brief   :  判断顺序栈是否为空
  69. * @param   :  
  70. *              @manager:需要判断的顺序栈
  71. * @retval  :  为空返回true,不为空返回false
  72. * @date    :  2025/7/5
  73. * @version :  1.0
  74. * @note    :  None
  75. */
  76. bool SeqStack_IsEmpty(SeqStack_t *manager)
  77. {
  78.     return (-1==manager->top)? true : false;
  79. }
  80. /**
  81. * @name    :  SeqStack_Push
  82. * @brief   :  入栈
  83. * @param   :  
  84. *              @manager:需要操作的顺序栈
  85. *              @Data:需要压入的元素
  86. * @retval  :  成功返回true,失败返回false
  87. * @date    :  2025/7/5
  88. * @version :  1.0
  89. * @note    :  None
  90. */
  91. bool SeqStack_Push(SeqStack_t *manager,DataType_t Data)
  92. {
  93.     //判断顺序栈是否已满
  94.     if ( SeqStack_IsFull(manager) ){
  95.         printf("Sequence Stack is Full\n");
  96.         return false;
  97.     }
  98.     //如果顺序栈有剩余空间,则把新元素插入顺序栈的栈顶,并更新管理结构体中最后栈顶的元素下标
  99.     manager->bottom[++manager->top]=Data;
  100.     return true;
  101. }
  102. /**
  103. * @name    :  SeqStack_Pop
  104. * @brief   :  出栈
  105. * @param   :  
  106. *              @manager:需要操作的顺序栈
  107. *              @out:接收弹出的值
  108. * @retval  :  成功返回true,失败返回false
  109. * @date    :  2025/7/5
  110. * @version :  1.0
  111. * @note    :  None
  112. */
  113. bool SeqStack_Pop(SeqStack_t *manager, DataType_t *out)
  114. {   
  115.     //判断顺序栈是否为空
  116.     if ( SeqStack_IsEmpty(manager) ){
  117.         printf("Sequence Stack is empty\n");
  118.         return false;
  119.     }
  120.     //删除一个元素,栈顶元素下标-1
  121.     *out=manager->bottom[manager->top--];
  122.     return true;
  123. }
  124. /**
  125. * @name    :  SeqStack_print
  126. * @brief   :  遍历顺序栈
  127. * @param   :  
  128. *              @manager:需要操作的顺序栈
  129. * @retval  :  成功返回true,失败返回false
  130. * @date    :  2025/7/5
  131. * @version :  1.0
  132. * @note    :  None
  133. */
  134. void SeqStack_print(SeqStack_t *manager)
  135. {  
  136.      //判断顺序栈是否为空
  137.     if ( SeqStack_IsEmpty(manager) ){
  138.         printf("Sequence Stack is empty\n");
  139.         return;
  140.     }
  141.     //如果顺序栈不为空,则打印顺序栈的元素
  142.     for (int i=0;i<manager->top+1;i++){
  143.         printf("Element[%d]=%d\n",i,manager->bottom[i]);
  144.     }
  145.     printf("\n");
  146. }
  147. /**
  148. * @name    :  Enqueue
  149. * @brief   :  入队
  150. * @param   :  
  151. *              @s1:传入栈s1
  152. *              @s2:传入栈s2
  153. *              @Data:需要入队的数据
  154. * @retval  :  成功返回true,失败返回false
  155. * @date    :  2025/7/5
  156. * @version :  1.0
  157. * @note    :  使用栈s1和栈s2模拟队列,具有“先进先出”的思想
  158. */
  159. bool Enqueue(SeqStack_t *s1,SeqStack_t *s2,DataType_t Data)
  160. {
  161.     DataType_t temp;//用于临时存储栈1出栈到栈2的元素的值
  162.    
  163.     //判断栈1是否已满(已满 OR 未满)
  164.     if(s1->top+1>=maxSize){
  165.         //判断栈2是否为空(空 OR 不空)
  166.         if(SeqStack_IsEmpty(s2)){
  167.             //栈2为空,一次将栈1中的元素出战到栈2
  168.             while(!SeqStack_IsEmpty(s1)){
  169.                 if(!SeqStack_Pop(s1,&temp)) return false;
  170.                 if(!SeqStack_Push(s2,temp)) return false;
  171.             }
  172.             //栈1为空后,将元素插入栈1
  173.             if(!SeqStack_Push(s1,Data)) return false;
  174.             return true;
  175.         }else{
  176.             //栈2不为空,无法入队
  177.             return false;
  178.         }
  179.     }else{
  180.         // 栈1未满,直接将元素插入栈1
  181.         if(!SeqStack_Push(s1,Data)) return false;
  182.     }
  183.     return true;
  184. }
  185. /**
  186. * @name    :  Dequeue
  187. * @brief   :  出队
  188. * @param   :  
  189. *              @s1:传入栈s1
  190. *              @s2:传入栈s2
  191. *              @out:接收出队的数据
  192. * @retval  :  成功返回true,失败返回false
  193. * @date    :  2025/7/5
  194. * @version :  1.0
  195. * @note    :  使用栈s1和栈s2模拟队列,具有“先进先出”的思想
  196. */
  197. bool Dequeue(SeqStack_t *s1,SeqStack_t *s2,DataType_t *out)
  198. {
  199.     DataType_t temp;//用于临时存储栈1出栈到栈2的元素的值
  200.     //判断队列是否为空,即栈1和栈2是否都为空(空 OR 不空)
  201.     if(SeqStack_IsEmpty(s1) && SeqStack_IsEmpty(s2)){
  202.         return false;
  203.     }else{
  204.         //判断栈2是否为空(空 OR 不空)
  205.         if(!SeqStack_IsEmpty(s2)){
  206.             //栈2不为空,直接出栈
  207.             if(!SeqStack_Pop(s2,out)) return false;
  208.         }else{
  209.             //栈2为空,将栈1内的元素一次出栈到栈2
  210.             while(!SeqStack_IsEmpty(s1)){
  211.                 if(!SeqStack_Pop(s1,&temp)) return false;
  212.                 if(!SeqStack_Push(s2,temp)) return false;
  213.             }
  214.             //栈1为空后,将栈2元素出栈
  215.             if(!SeqStack_Pop(s2,out)) return false;
  216.         }
  217.     }
  218.     return true;
  219. }
复制代码
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册