循环队列的实现

目录

顺序队列简介

循环队列的诞生

设计循环队列


顺序队列简介

当使用动态顺序表实现队列时,除了用一组地址连续的存储单元依次存放队头元素到队尾元素外,还需要两个指针head(指示队头元素的位置)tail(指示队尾元素的位置);

typedef int QDataType;
typedef struct Queue
{
   QDataType* nums;
   int head;
   int tail;
}Queue;

循环队列的实现_第1张图片

当在顺序队列中插入队列尾元素A1,尾指针自增1,那么顺序队列何时为空队列?

循环队列的实现_第2张图片

由上图可知尾指针tail始终指向队列尾元素的下一个位置;那么当tail=0时,顺序队列为空队列,此时head=0;

当在顺序队列中删除队列头元素A1,头指针自增1,此时队列依然为空且head=tail;

循环队列的实现_第3张图片

总结:

  • 队列为空队列时front=tail
  • 插入新的队尾元素时,尾指针增1;删除队列头元素时,头指针增1;
  • 非空队列中,头指针始终指向队列头元素,尾指针始终指向队列尾元素的下一个位置;

假设为队列长度为MaxSize,若队列处于如下图所示情形,队列是否可以插入新的元素?

循环队列的实现_第4张图片

不可继续插入新的队尾元素,原因就是数组越界访问导致程序崩溃;由于队列的空间是由动态内存函数所开辟而且删除数据时空间并未释放,空间一直可以重复利用,而当处于上述情形队列实际可用空间并未占满——假溢出现象

循环队列的诞生

下图所述情形,存在未占用的空间,要求插入新的队尾元素而且不可导致数组越界,该如何实现?

循环队列的实现_第5张图片

 记N=MaxSize, 对于任意正整数X,当XN时,若X线性增长,X%N的范围为1 2 3 ……N-1;

而此时队尾指针tail=MaxSize,tail%MaxSize==0,回到起始位置,构成了一种循环,称之为循环队列;

循环队列是一种具有固定大小的队列,本质为数组,利用了取模运算,逻辑上将一条链变成了一个环;     

假设队列初始情况如下图:

循环队列的实现_第6张图片

上图,队列头元素为A1,队列尾元素为A3

如果插入A4,A5,A6,  存在关系式head=tail,如图一所示;

如果删除A1,A2,A3,存在关系式head=tail,如图二所示;

循环队列的实现_第7张图片

无论队列为空还是满,都有关系式tail=head存在,根据关系式无法判断队列为空还是满,如何解决这个问题?

方案一:

设计一个标志位记录队列中元素个数以区别队列为队空或队满

初始化队列时flag=0;

当元素入队列时,flag++;             当元素出队列时,falg--;

当flag==MaxSize时,队列已满;当flag==0时,队列为空;

方案二:

多开辟一个元素空间,当队列的对头指针位于队尾指针的下一个位置,说明队列已满;

当队头指针与队尾指针位置相同时,说明队列为空

下述采取方案二实现循环队列;

  • 队满情况

当队列处于图2情况tail+1=head就可说明队满,但是当队列处于图1情形不能用tail+1=head这个关系式表述队满,那么队满该如何描述?

队满描述: (tail+1)%MaxSize==head;

循环队列的实现_第8张图片

  • 队空情况

无论处于图1还是图二情形,都可以用表达式tail=head,描述队列为空;

队空描述:tail=head;

循环队列的实现_第9张图片

设计循环队列

循环队列接口展示:

  • MyCircularQueue(k): 构造器,设置队列长度为 k 。
  • Front: 从队首获取元素。如果队列为空,返回 -1 。
  • Rear: 获取队尾元素。如果队列为空,返回 -1 。
  • enQueue(value): 向循环队列插入一个元素。如果成功插入则返回真。
  • deQueue(): 从循环队列中删除一个元素。如果成功删除则返回真。
  • isEmpty(): 检查循环队列是否为空。
  • isFull(): 检查循环队列是否已满

题目链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

//循环队列的结构
typedef struct 
{
  int* nums;
  int head;
  int tail;
  int k;
} MyCircularQueue;

//循环队列初始化
MyCircularQueue* myCircularQueueCreate(int k) 
{
    MyCircularQueue* ps=(MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    //k+1=maxsize,多开辟1个空间用来判断队列是否满
    ps->nums=(int*)malloc(sizeof(ps->nums)*(k+1));
    ps->head=ps->tail=0;
    ps->k=k;

    return ps;
}

//判空
bool myCircularQueueIsEmpty(MyCircularQueue* obj) 
{
    //tail=head 说明队列为空
 return obj->tail==obj->head;
}

//判满
bool myCircularQueueIsFull(MyCircularQueue* obj) 
{
   //tail的下一个为head 说明队列已满 (tail+1)%(maxsize)==head;
   return (obj->tail+1)%(obj->k+1)==obj->head;
}

//入队列
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) 
{
        //入队,先进行判满
        if(myCircularQueueIsFull(obj))
        {
            return false;
        }
        else
        {
            //队列未满,队尾指针tail总是指向队尾元素的下一个位置;
            obj->nums[obj->tail]=value;
            obj->tail++;
            //防止数组越界,有可能tail刚好处于队列元素已满的位置;
            obj->tail%=(obj->k+1);
            return true;
        }
}

//出队列
bool myCircularQueueDeQueue(MyCircularQueue* obj) 
{
   //判空
   if(myCircularQueueIsEmpty(obj))
   {
       return false;
   }
   else
   {
       //删除一个元素,只需要头指针head向后移动1步,空间不释放,重复利用;
       obj->head++;
       obj->head%=(obj->k+1);
       return true;
   }
}

//获取队头元素
int myCircularQueueFront(MyCircularQueue* obj) 
{
    //队列为空返回-1;
    if(myCircularQueueIsEmpty(obj))
    {
        return -1;
    }
    //队头指针始终指向对头元素的位置;
    return obj->nums[obj->head];
}

//获取队尾元素
int myCircularQueueRear(MyCircularQueue* obj) 
{
     //队列为空返回-1;
    if(myCircularQueueIsEmpty(obj))
    {
        return -1;
    }
    //队尾指针tail始终指向队尾元素的下一个位置;
    //寻找队尾元素的方法:由于循环队列的长度(包含多出来的一个位置)为maxsize=k+1;
    //尾指针向后走K+1,相当于绕循环队列一圈,回到尾指针原先位置;
    //尾指针向后走k步,相当于回到尾指针原先位置的上一个位置,相当于队尾元素的位置;
    //为了实现循环,只需要%maxsize即可;
    return obj->nums[(obj->tail+obj->k)%(obj->k+1)];
}

//销毁循环队列
void myCircularQueueFree(MyCircularQueue* obj) 
{
    free(obj->nums);
    free(obj);
}

你可能感兴趣的:(数据结构,c语言)