眺愤 发表于 2025-7-16 14:38:49

循环队列的接口

目录


目录

[*]目录
[*]队列的概念

[*]假溢出问题

[*]循环队列的实现

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

[*]两个栈实现队列

队列的概念

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

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

解决方案:

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

1.以数组为基础实现循环队列

/**
* @file name : Cirqueue.c
* @brief   :该程序以数组为基础实现循环队列元素的初始化、入队、出队、遍历,另外为了提高可移植性,所以循环队列中
*               数据元素的类型为DataType_t,用户可以根据实际情况修改循环队列中元素的类型
* @author    :MINDSETT@163.com
* @date      :2025/7/5
* @version   :1.0
* @note      :以数组末尾浪费一个存储空间为代价来换取更简便的代码
* CopyRight (c)2025MINDSETT@163.comAll Right Reserved
*/

#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>

//指定循环队列中的元素的数据类型,用户可根据需要进行修改
typedef int DataType_t;

//构造一个实现循环队列的各项参数(循环队列首地址+循环队列的容量+队尾下标+队首下标)的结构体
typedef struct Circular_Queue
{
    DataType_t *addr;    //循环队列首地址
    unsigned intsize;      //循环队列的容量
    int         rear;       //队尾下标
    int         front;       //队首下标
}Cirqueue_t;


/**
* @name    :Cirqueue_Create
* @brief   :创建一个循环队列并进行初始化
* @param   :None
* @retval:返回栈底地址
* @date    :2025/7/5
* @version :1.0
* @note    :None
*/
Cirqueue_t *Cirqueue_Create(unsigned int size)
{
    //1.利用calloc对循环队列的管理结构体申请一个内存
    Cirqueue_t *manager=(Cirqueue_t *)calloc(1,sizeof(Cirqueue_t));
    if (NULL == manager){
      perror("calloc memory for manager is failed\n");
      exit(-1);
    }
    //2.对循环队列管理结构体的所有元素进行初始化
    manager->addr=(DataType_t *)calloc(size,sizeof(DataType_t));
    if (NULL == manager->addr){
      perror("calloc memory for Stack is failed\n");
      exit(-1);
    }
    manager->size=size;
    manager->rear=0;
    manager->front=0;
    return manager;
}


/**
* @name    :Cirqueue_IsFull
* @brief   :判断循环队列是否已满
* @param   :
*            @manager:需要判断的循环队列
* @retval:已满返回true,未满返回false
* @date    :2025/7/5
* @version :1.0
* @note    :None
*/
bool Cirqueue_IsFull(Cirqueue_t *manager)
{
    return ( ( (manager->rear+1)%manager->size )==manager->front )? true : false;
}

/**
* @name    :Cirqueue_IsEmpty
* @brief   :判断循环队列是否为空
* @param   :
*            @manager:需要判断的循环队列
* @retval:为空返回true,不为空返回false
* @date    :2025/7/5
* @version :1.0
* @note    :None
*/
bool Cirqueue_IsEmpty(Cirqueue_t *manager)
{
    return (manager->rear==manager->front)? true : false;
}

/**
* @name    :Cirqueue_Enqueue
* @brief   :入队
* @param   :
*            @manager:需要操作的循环队列
*            @Data:需要入队的元素
* @retval:已满返回true,未满返回false
* @date    :2025/7/5
* @version :1.0
* @note    :None
*/
bool Cirqueue_Enqueue(Cirqueue_t *manager,DataType_t Data)
{
    //判断循环队列是否已满
    if ( Cirqueue_IsFull(manager) ){
      printf("Circular Queue is Full\n");
      return false;
    }
    //如果循环队列有剩余空间,则把新元素插入循环队列的队尾
    manager->addr=Data;
    //防止队尾下标越界
    manager->rear=(manager->rear+1)%manager->size;
    return true;
}



/**
* @name    :Cirqueue_Dequeue
* @brief   :出队
* @param   :
*            @manager:需要操作的循环队列
* @retval:成功返回出队的元素
* @date    :2025/7/5
* @version :1.0
* @note    :None
*/
DataType_t Cirqueue_Dequeue(Cirqueue_t *manager)
{   
    //判断循环队列是否为空
    if ( Cirqueue_IsEmpty(manager) ){
      printf("Circular Queue is empty\n");
      return false;
    }
    //定义临时变量存储要出队的元素
    DataType_t temp=0;
    //队首元素出队,队首下标+1
    temp=manager->addr;
    //防止队首下标越界
    manager->front=(manager->front+1)%manager->size;
    return temp;
}

/**
* @name    :Cirqueue_Print
* @brief   :遍历
* @param   :
*            @manager:需要操作的循环队列
* @retval:成功返回true,失败返回false
* @date    :2025/7/5
* @version :1.0
* @note    :None
*/
bool Cirqueue_Print(Cirqueue_t *manager)
{
    if (Cirqueue_IsEmpty(manager)) {
      printf("Circular Queue is empty\n");
      return false;
    }
    int i = manager->front;
    printf("Queue: ");
    while (i != manager->rear) {
      printf("%d ", manager->addr);
      i = (i + 1) % manager->size;
    }
    printf("\n");
    return true;
}2.以链表为基础实现循环队列

/**
* @file name : CirLnList_Queue.c
* @brief   :该程序以链表为基础实现循环队列元素的初始化、入队、出队、遍历,另外为了提高可移植性,所以循环队列中
*               数据元素的类型为DataType_t,用户可以根据实际情况修改循环队列中元素的类型
* @author    :MINDSETT@163.com
* @date      :2025/7/11
* @version   :1.0
* @note      :头结点不存储数据,仅用于管理队列
* CopyRight (c)2025MINDSETT@163.comAll Right Reserved
*/

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
//指定循环队列中元素的数据类型,用户可根据需要进行修改
typedef int DataType_t;

//构造循环队列的结点,循环队列中所有结点的数据类型应该是相同的(数据域+指针域)
typedef struct CircularLinkedList_Queue
{
    DataType_t                  data;   //结点的数据域
    struct CircularLinkedList_Queue *   next;   //结点的指针域
}CirLnList_Queue_t;


/**
* @name    :Queue_Create
* @brief   :创建一个空循环队列,空循环队列应该有一个头节点,对循环队列进行初始化
* @param   :None
* @retval:返回头结点的地址
* @date    :2025/7/11
* @version :1.0
* @note    :None
*/
CirLnList_Queue_t *Queue_Create(void)
{
    //创建一个头结点并对头结点申请内存
    CirLnList_Queue_t* Head=(CirLnList_Queue_t*)calloc(1,sizeof(CirLnList_Queue_t));
    if (NULL==Head){
      perror("calloc memory for Head is failed\n");
      exit(-1);
    }
    //对头结点进行初始化,头结点不存储数据域,指针域指向自身,体现“循环”思想
    Head->next=Head;
    //将头结点的地址返回
    return Head;
}


/**
* @name    :Queue_NewNode
* @brief   :创建一个新结点,并为新结点申请堆内存以及对新结点的数据域和指针域进行初始化
* @param   :
*             @data:新结点的数据
* @retval:返回新结点的地址
* @date    :2025/7/11
* @version :1.0
* @note    :None
*/
CirLnList_Queue_t * Queue_NewNode(DataType_t data)
{
    //创建新结点,并未新结点申请堆内存
    CirLnList_Queue_t * New=(CirLnList_Queue_t*)calloc(1,sizeof(CirLnList_Queue_t));
   if (NULL==New){
      perror("calloc memory for NewNode is failed\n");
      return NULL;
    }
    //对新结点的数据域和指针域进行初始化
    New->data=data;
    New->next=NULL;
    return New;
}

/**
* @name    :Queue_Enqueue
* @brief   :入队
* @param   :
*             @Head:插入的循环队列
*             @data:新结点的数据
* @retval:成功返回true,失败返回false
* @date    :2025/7/11
* @version :1.0
* @note    :None
*/
bool Queue_Enqueue(CirLnList_Queue_t * Head,DataType_t data)
{
    //创建新结点,并对新结点进行初始化
    CirLnList_Queue_t* New=Queue_NewNode(data);
    if (NULL==New){
      perror("Create New node is failed\n");
      return false;
    }
    //判断循环队列是否为空,如果为空,则将新结点直接插入
    if (Head==Head->next){
      Head->next=New;
      New->next=New;//指针域指向自身,体现“循环”思想
      return true;
    }
    //备份首结点的地址
    CirLnList_Queue_t * tail=Head->next;
    //找到尾结点
    while( tail->next != Head->next ){
      tail=tail->next;
    }
    //新结点指向首结点,旧尾结点指向新结点
    New->next=tail->next;
    tail->next=New;

    return true;
}

/**
* @name    :Queue_Dequeue
* @brief   :出队
* @param   :
*             @Head:循环队列的头结点
* @retval:成功返回true,失败返回false
* @date    :2025/7/11
* @version :1.0
* @note    :None
*/
bool Queue_Dequeue(CirLnList_Queue_t* Head)
{
    //备份首结点
    CirLnList_Queue_t * first=Head->next;
    CirLnList_Queue_t * tail=Head->next;
    //判断传参错误或循环队列为空
    if (NULL==Head || Head==Head->next){
      perror("Linked list is empty\n");
      return false;
    }
    //1.只有一个首结点
    if (Head->next->next==Head->next){
      Head->next=Head;
      first->next=NULL;//首结点的指针域指向NULL,从循环队列中断开(避免野指针)
      free(first);
      return true;
    }
    //2.循环队列有多个结点
    //找到尾结点
    while( tail->next!=Head->next ){

      tail=tail->next;
    }
    //尾结点指向首结点的直接后驱
    tail->next=first->next;
    //将头结点指针域指向首结点的直接后驱
    Head->next=first->next;
    //旧的首结点的指针域指向NULL,从循环队列中断开(避免野指针)
    first->next=NULL;
    //释放首结点占用的内存
    free(first);

    return true;
}

/**
* @name    :Queue_Print
* @brief   :遍历输出循环队列中各个结点的数据
* @param   :
*             @Head:需要遍历的循环队列
* @retval:成功返回true,失败返回false
* @date    :2025/7/11
* @version :1.0
* @note    :None
*/
bool Queue_Print(CirLnList_Queue_t* Head)
{
    //判断传参错误或循环队列为空
    if (NULL==Head || Head==Head->next){
      perror("Current CircularLinkedList is empty\n");
      return false;
    }
    //对循环队列的头节点的地址进行备份
    CirLnList_Queue_t *curr=Head;
    //循环遍历循环队列
    while(curr->next){
      curr=curr->next;
      printf("Data=%d ",curr->data);
      if( curr->next == Head->next){
            break;
      }
    }

    return true;
}两个栈实现队列


// 基本思想:利用栈s1和栈s2实现队列,栈的思想是“先进后出”,队列的思想是“先进先出”,可以选择把栈s1作为入队缓存,栈s2作为出队缓存//代码实现:在栈的基础上使用两个栈实现循环队列的入队和出队

/**
* @file name : Stack_Queue.c
* @brief   :该程序以栈为基础实现循环队列元素的初始化、入队、出队、遍历,另外为了提高可移植性,所以循环队列中
*               数据元素的类型为DataType_t,用户可以根据实际情况修改循环队列中元素的类型
* @author    :MINDSETT@163.com
* @date      :2025/7/5
* @version   :1.0
* @note      :None
* CopyRight (c)2025MINDSETT@163.comAll Right Reserved
*/

#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>

#define maxSize 10

//指定顺序栈中的元素的数据类型,用户可根据需要进行修改
typedef int DataType_t;

//构造一个实现顺序栈的各项参数(栈底地址+栈容量+栈顶元素的下标)的结构体
typedef struct Sequence_Stack
{
    DataType_t *bottom;    //栈底地址
    unsigned intsize;      //栈容量
    int         top;       //栈顶元素的下标
}SeqStack_t;


/**
* @name    :SeqStack_Create
* @brief   :创建一个顺序栈并进行初始化
* @param   :None
* @retval:返回栈底地址
* @date    :2025/7/5
* @version :1.0
* @note    :None
*/
SeqStack_t *SeqStack_Create(unsigned int size)
{
    //1.利用calloc对顺序栈的管理结构体申请一个内存
    SeqStack_t *manager=(SeqStack_t *)calloc(1,sizeof(SeqStack_t));
    if (NULL == manager){
      perror("calloc memory for manager is failed\n");
      exit(-1);
    }
    //2.对顺序栈管理结构体的所有元素进行初始化
    manager->bottom=(DataType_t *)calloc(size,sizeof(DataType_t));
    if (NULL == manager->bottom){
      perror("calloc memory for Stack is failed\n");
      exit(-1);
    }
    manager->size=size;
    manager->top=-1;
    return manager;
}


/**
* @name    :SeqStack_IsFull
* @brief   :判断顺序栈是否已满
* @param   :
*            @manager:需要判断的顺序栈
* @retval:已满返回true,未满返回false
* @date    :2025/7/5
* @version :1.0
* @note    :None
*/
bool SeqStack_IsFull(SeqStack_t *manager)
{
    return (manager->top+1==manager->size)? true : false;
}

/**
* @name    :SeqStack_IsEmpty
* @brief   :判断顺序栈是否为空
* @param   :
*            @manager:需要判断的顺序栈
* @retval:为空返回true,不为空返回false
* @date    :2025/7/5
* @version :1.0
* @note    :None
*/
bool SeqStack_IsEmpty(SeqStack_t *manager)
{
    return (-1==manager->top)? true : false;
}

/**
* @name    :SeqStack_Push
* @brief   :入栈
* @param   :
*            @manager:需要操作的顺序栈
*            @Data:需要压入的元素
* @retval:成功返回true,失败返回false
* @date    :2025/7/5
* @version :1.0
* @note    :None
*/
bool SeqStack_Push(SeqStack_t *manager,DataType_t Data)
{
    //判断顺序栈是否已满
    if ( SeqStack_IsFull(manager) ){
      printf("Sequence Stack is Full\n");
      return false;
    }
    //如果顺序栈有剩余空间,则把新元素插入顺序栈的栈顶,并更新管理结构体中最后栈顶的元素下标
    manager->bottom[++manager->top]=Data;
    return true;
}



/**
* @name    :SeqStack_Pop
* @brief   :出栈
* @param   :
*            @manager:需要操作的顺序栈
*            @out:接收弹出的值
* @retval:成功返回true,失败返回false
* @date    :2025/7/5
* @version :1.0
* @note    :None
*/
bool SeqStack_Pop(SeqStack_t *manager, DataType_t *out)
{   
    //判断顺序栈是否为空
    if ( SeqStack_IsEmpty(manager) ){
      printf("Sequence Stack is empty\n");
      return false;
    }

    //删除一个元素,栈顶元素下标-1
    *out=manager->bottom;

    return true;
}


/**
* @name    :SeqStack_print
* @brief   :遍历顺序栈
* @param   :
*            @manager:需要操作的顺序栈
* @retval:成功返回true,失败返回false
* @date    :2025/7/5
* @version :1.0
* @note    :None
*/
void SeqStack_print(SeqStack_t *manager)
{
   //判断顺序栈是否为空
    if ( SeqStack_IsEmpty(manager) ){
      printf("Sequence Stack is empty\n");
      return;
    }
    //如果顺序栈不为空,则打印顺序栈的元素
    for (int i=0;i<manager->top+1;i++){
      printf("Element[%d]=%d\n",i,manager->bottom);
    }
    printf("\n");
}


/**
* @name    :Enqueue
* @brief   :入队
* @param   :
*            @s1:传入栈s1
*            @s2:传入栈s2
*            @Data:需要入队的数据
* @retval:成功返回true,失败返回false
* @date    :2025/7/5
* @version :1.0
* @note    :使用栈s1和栈s2模拟队列,具有“先进先出”的思想
*/
bool Enqueue(SeqStack_t *s1,SeqStack_t *s2,DataType_t Data)
{
    DataType_t temp;//用于临时存储栈1出栈到栈2的元素的值
   
    //判断栈1是否已满(已满 OR 未满)
    if(s1->top+1>=maxSize){
      //判断栈2是否为空(空 OR 不空)
      if(SeqStack_IsEmpty(s2)){
            //栈2为空,一次将栈1中的元素出战到栈2
            while(!SeqStack_IsEmpty(s1)){
                if(!SeqStack_Pop(s1,&temp)) return false;
                if(!SeqStack_Push(s2,temp)) return false;
            }
            //栈1为空后,将元素插入栈1
            if(!SeqStack_Push(s1,Data)) return false;
            return true;
      }else{
            //栈2不为空,无法入队
            return false;
      }
    }else{
      // 栈1未满,直接将元素插入栈1
      if(!SeqStack_Push(s1,Data)) return false;
    }

    return true;
}

/**
* @name    :Dequeue
* @brief   :出队
* @param   :
*            @s1:传入栈s1
*            @s2:传入栈s2
*            @out:接收出队的数据
* @retval:成功返回true,失败返回false
* @date    :2025/7/5
* @version :1.0
* @note    :使用栈s1和栈s2模拟队列,具有“先进先出”的思想
*/
bool Dequeue(SeqStack_t *s1,SeqStack_t *s2,DataType_t *out)
{
    DataType_t temp;//用于临时存储栈1出栈到栈2的元素的值
    //判断队列是否为空,即栈1和栈2是否都为空(空 OR 不空)
    if(SeqStack_IsEmpty(s1) && SeqStack_IsEmpty(s2)){
      return false;
    }else{
      //判断栈2是否为空(空 OR 不空)
      if(!SeqStack_IsEmpty(s2)){
            //栈2不为空,直接出栈
            if(!SeqStack_Pop(s2,out)) return false;
      }else{
            //栈2为空,将栈1内的元素一次出栈到栈2
            while(!SeqStack_IsEmpty(s1)){
                if(!SeqStack_Pop(s1,&temp)) return false;
                if(!SeqStack_Push(s2,temp)) return false;
            }
            //栈1为空后,将栈2元素出栈
            if(!SeqStack_Pop(s2,out)) return false;
      }
    }

    return true;
}
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: 循环队列的接口