【数据结构】设计循环队列

设计你的循环队列实现。 循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。

  • ①.MyCircularQueue(k)设置队列长度为 k 。
  • ②.isEmpty(): 检查循环队列是否为空。
  • ③.isFull(): 检查循环队列是否已满。
  • ④.enQueue(value): 向循环队列插入一个元素。
  • ⑤.deQueue(): 从循环队列中删除一个元素。
  • ⑥.Front: 从队首获取元素 。
  • ⑦.Rear: 获取队尾元素。
  • ⑧.myCircularQueueFree 销毁循环队列

通常队列是用链表来实现,不过在这里循环队列既可以使用数组实现,也可以用链表实现。
数组设计循环队列的好处:
1.找队尾元素方便,链表实现需要记住前面的节点,需要找尾。
2.循环实现方便,只要让最后一个元素下标模上数组的大小,就可以回到数组首元素位置。而链表如果只有使用循环双向链表才可以达到方便的效果。

  • 数组实现循环队列的思想
    数组需要多开辟一个空间,以达到循环效果。
    front用来追踪队头元素
    rear用来追踪队尾元素
    【数据结构】设计循环队列_第1张图片
    一开始front和rear都指向数组开头位置,当有元素插入进来时,rear跟着往后走。插入一个元素,rear走一步,插入两个元素,rear走两步,插入三个元素,rear走三步。当队列中满了无法插入时,就不能再走了。
    【数据结构】设计循环队列_第2张图片
    队尾元素的位置就是rear-1.
    现在front的位置就是队头的位置,当有元素被删除时,front就往后走,删除一个元素,front就完后走,删除一个元素就往后走(注意这是队列,删除数据是从队头删除),直到队列中元素都删除光了,就不能走了。
    【数据结构】设计循环队列_第3张图片
    所以队头元素的位置就是front的位置。
    那如何实现循环的呢?
    因为我们给数组多开了一个空间,当队列满了时候无法插入了,删除队头元素后,队头空间就留出来了,这时只要往多开辟的空间里插入元素,rear再往后走,不过rear是回到最开始的位置,也就是队头位置了。
    这时多开辟的位置就起到了暂时存储数据的作用。当队列满了就不能插入了。
    【数据结构】设计循环队列_第4张图片
    而什么情况算满了呢?当rear+1=front就说明队列已经满了,上图就是队列已满的情况,可能有人会说
    【数据结构】设计循环队列_第5张图片
    这样不符合呀,这不是也满了嘛,对呀这是队列已经满了,不过这里的rear+1后就是回到数组首元素的位置上了呀,所以是符合情况的。
    【数据结构】设计循环队列_第6张图片

①.MyCircularQueue(k)设置队列长度为 k 。

typedef struct {
	int* a;
	int front;
	int rear;
	int k;
} MyCircularQueue;//首先定义一个队列,这个队列由数组a,下标front,rear还有数组大小k构成
//为什么要给k呢?因为不给k数组的大小的话,后面需要取模就不好取模了=
MyCircularQueue* myCircularQueueCreate(int k)//定义一个指向队列的指针  
{
   //队列的空间需要动态开辟,还有队列里面的数组空间也需要动态开辟,所以需要开辟两个空间,最后释放也需要释放两个空间。
   
	MyCircularQueue* queue = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
	queue->a = (int*)malloc(sizeof(int) * (k + 1));//数组需要多开辟一个空间
	queue->front = queue->rear = 0;//一开始都指向0
	queue->k = k;//数组大小
	return queue;
}	

②.isEmpty(): 检查循环队列是否为空。

如何判断队列是否为空呢?
当一开始队列中没有元素时,rear和front都指向0表明队列为空,但是不一定都必须在0这个位置,只要当rear和front都相等时,就说明队列是空的。

bool myCircularQueueIsEmpty(MyCircularQueue* obj)
{
	return obj->front == obj->rear;
}

③.isFull(): 检查循环队列是否已满。

本质上就是rear+1=front就表示队列已经满了,
【数据结构】设计循环队列_第7张图片
不过这样的情况可能不满足,我们分析rear如果加1那肯定不会等于front的呀。
【数据结构】设计循环队列_第8张图片
这样队列也是满的不过队列比较特殊,我们需要当rear+1后再模上数组总长度也就是k+1.
我们知道当一个数模上一个比它大的数时,是没有影响的,所以只有当rear+1时等于数总长度,模完等于0又回到首元素的位置去,其他位置上rear模上k+1是没有影响的。

bool myCircularQueueIsFull(MyCircularQueue* obj)
{
	return (obj->rear + 1) % (obj->k + 1) == obj->front;
}

④.enQueue(value): 向循环队列插入一个元素。

向循环队列插入一个元素。如果成功插入则返回真。

向循环队列中插入一个元素时,rear就要跟着往后走。
并且要注意的是,每次rear走完后都需要取模一下,当最后一个位置又元素插入时,rear就需要回到数组首元素的位置,那如何回去呢?就是根据取模就可以让rear返回去,每次rear++完,让rear模上数组总长度也就是k+1,如果,最后一位置插入元素了,那么rear++完就变成0.

还要注意的是,在插入之前我们需要考虑,队列是否已经满了,当队列满了时候,就无法插入进去了。
所以代码如下:

bool myCircularQueueEnQueue(MyCircularQueue* obj, int value)
{
	if (myCircularQueueIsFull(obj))//在插入之前判断是否队列满了
	{
		return false;
	}
	else
	{
		obj->a[obj->rear++] = value;
		obj->rear %= obj->k + 1;//每次插入,rear往后走完,都需要进行取模,防止走出数组范围
	}
}

⑤.deQueue(): 从循环队列中删除一个元素。

从循环队列中删除一个元素。如果成功删除则返回真。

在删除元素之前需要考虑队列是否为空,如果为空,那就不能再删了。
因为front就是队头元素的位置,当删除一个元素时,front就往后走,删除一个元素就往走,这里注意front每往后走一步都需要取模,道理一样,防止front走过头,当front在数组最后一个位置时,再删除元素呢,front就要变成0了。

bool myCircularQueueDeQueue(MyCircularQueue* obj)
{
	if (myCircularQueueIsEmpty(obj))
	{
		return false;
	}
	else
	{
		++obj->front;
		obj->front %= obj->k + 1;
	}
	
}

⑥.Front: 从队首获取元素 。

获取队头数据,很简单,因为front的位置就是队头的数据
,但要注意的是当队列为空时,那就没有数据可获取了,需要讨论下。


int myCircularQueueFront(MyCircularQueue* obj)
{
	if (myCircularQueueIsEmpty(obj))
	{
		return -1;//如果为空,则返回-1;
	}
	else
	{
		return obj->a[obj->front];
	}
}

⑦.Rear: 获取队尾元素。

与获取队头数据一样,在获取之前需要判断一下队列是否为空
获取队尾数据,也简单,队尾数据的位置不就是rear-1嘛。
不过这里有个特例,当数组最后一个元素插入进来,rear的值就需要更新成0了,
那这时再获取队尾数据位置,还是rear-1吗?
当然不是了,rear-1不就是-1了嘛。
所以我们这里可以分两种情况讨论,定义一个变量x,当rear==k+1时,就将x=k,
当rear!=k+1时,那x=rear-1; x位置就是队尾数据。

int myCircularQueueRear(MyCircularQueue* obj)
{
	if (myCircularQueueIsEmpty(obj))
	{
		return -1;
	}
	else
	{
		int x = obj->rear == obj->k + 1 ? obj->k : obj->rear - 1;
		return obj->a[x];
	}

}

【数据结构】设计循环队列_第9张图片
这里还有第二种解决方法,rear-1加上k+1再模上k+1
原理是什么呢?
一个数模上一个比它大的数是不变的,rear-1+k+1就可以回到原来的位置,这个是针对上图情况,再模上k+1,就是针对所有情况了。

int myCircularQueueRear(MyCircularQueue* obj)
{
	if (myCircularQueueIsEmpty(obj))
	{
		return -1;
	}
	else
	{
		return obj->a[(obj->rear - 1 + obj->k + 1) % (obj->k + 1)];
	}

}

⑧.myCircularQueueFree 销毁循环队列

因为一开始动态开辟了两个空间,一个队列空间,一个数组空间,所以需要释放两个空间

void myCircularQueueFree(MyCircularQueue* obj)
{
	free(obj->a);
	free(obj);
}

你可能感兴趣的:(数据结构与算法,数据结构,链表,算法)