中等题 设计循环队列

本题来自:622. 设计循环队列 - 力扣(LeetCode)

目录

题面:


题面:

设计你的循环队列实现。 循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。

循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。

你的实现应该支持如下操作:

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

示例:

MyCircularQueue circularQueue = new MyCircularQueue(3); // 设置长度为 3
circularQueue.enQueue(1);  // 返回 true
circularQueue.enQueue(2);  // 返回 true
circularQueue.enQueue(3);  // 返回 true
circularQueue.enQueue(4);  // 返回 false,队列已满
circularQueue.Rear();  // 返回 3
circularQueue.isFull();  // 返回 true
circularQueue.deQueue();  // 返回 true
circularQueue.enQueue(4);  // 返回 true
circularQueue.Rear();  // 返回 4

提示:

  • 所有的值都在 0 至 1000 的范围内;
  • 操作数将在 1 至 1000 的范围内;
  • 请不要使用内置的队列库。

代码:

#include <stdio.h>
#include <malloc.h>

typedef struct MyCircularQueue
{
	int* _myqueue;
	int _front;
	int _tail;
	int _size;
	int _capacity;
} MyCircularQueue,mq;


MyCircularQueue* myCircularQueueCreate(int k)
{
	mq* pq = (mq*)malloc(sizeof(mq)); // 先申请操作变量的空间
	pq->_capacity = k; // 获取队列的大小
	pq->_size = 0; // size记录当前队列元素的个数
	pq->_myqueue = (int*)malloc(sizeof(int) * k); // 再申请环形队列的空间,这里用数组方法
	pq->_front = pq->_tail = 0; // 让头尾下标归零
	return pq; // 把这个环形队列打出去
}

bool myCircularQueueEnQueue(MyCircularQueue* obj, int value)
{
	if (obj->_size == obj->_capacity) // 对列中元素的数量和容量相等说明满了,弹出false
		return false;

	// 到这里说明对列不满
	obj->_myqueue[obj->_tail] = value;// 把元素放到对列尾tail处,队尾后移
	obj->_tail++;
	if (obj->_tail == obj->_capacity) // tail下标超过了队列的大小
                         // 说明对列的最后一个空间有数,但是对列未满
		obj->_tail = 0; // 让tail下标再指向对列空间的0地址处
	obj->_size++;//对列个数+1
	return true;
}

bool myCircularQueueDeQueue(MyCircularQueue* obj)
{
	if (obj->_size == 0) // 对列有效元素为空,无法删除
		return false;

	// 至少有一个元素
	obj->_front++;
	if (obj->_front == obj->_capacity) // 如果头下标也到了空间尾,并把空间尾删除了,下标指向了对列后面的空间,就把front指向0地址处
		obj->_front = 0;
	obj->_size--;// 对列个数-1
	return true;
}

int myCircularQueueFront(MyCircularQueue* obj)
{
	if (obj->_size == 0) // 队列为空,无法访问头对头
		return -1;

	return obj->_myqueue[obj->_front];
}

int myCircularQueueRear(MyCircularQueue* obj)
{
	if (obj->_size == 0)
		return -1;
	
	if (obj->_tail != 0) // 如果tail下标为0,说明队列尾部已满,但是对头空间空出来了
		return obj->_myqueue[obj->_tail - 1]; // tail下标始终指向的是实际队尾的下一个
	else
		return obj->_myqueue[obj->_capacity - 1]; // tail指向0地址处,就把最后一个地址的元素打出去
}

bool myCircularQueueIsEmpty(MyCircularQueue* obj)
{
	return !obj->_size; // 这里是队列元素的个数取反
}

bool myCircularQueueIsFull(MyCircularQueue* obj)
{
	return obj->_size == obj->_capacity ? true : false; // 如果队列满了就返回true
}

void myCircularQueueFree(MyCircularQueue* obj)
{
	free(obj->_myqueue); // 销毁队列
	free(obj); // 再销毁指向队列的变量
}

/**
 * Your MyCircularQueue struct will be instantiated and called as such:
 * MyCircularQueue* obj = myCircularQueueCreate(k);
 * bool param_1 = myCircularQueueEnQueue(obj, value);
 
 * bool param_2 = myCircularQueueDeQueue(obj);
 
 * int param_3 = myCircularQueueFront(obj);
 
 * int param_4 = myCircularQueueRear(obj);
 
 * bool param_5 = myCircularQueueIsEmpty(obj);
 
 * bool param_6 = myCircularQueueIsFull(obj);
 
 * myCircularQueueFree(obj);
*/

代码分析:

这道题吧,官方题解也写的非常乱,一些网课老师教的方法也过于麻烦。最常见的是申请k+1个节点的空间,用front和rear两个指针移动判断队列满不满啥的,还有这俩指针重合,是空是满的判断,总之就是特别麻烦。

所以,本人痛定思痛,果断舍弃了上面这种做法。那么,来讲讲我的方法吧。

队列通常有两种结构,一种是数组队列,一种是链表队列。

数组队列的优点是可以随机访问,缺点是空间固定,链表队列的优点就是大小不固定。

在这道题中,数组队列完美匹配了题意,每次给固定大小的k空间,那么我们只需要动态申请k个连续空间的数组交给一个数组指针来维护就可以了。

用人话说就是,题里给你这个k值,但是k值不固定,需要每次根据k的大小来申请空间。

所以,只能用一个指针来维护这个数组。在代码中,我用的是int*mequeue来维护空间。

这块儿的代码主要实现在queuecreate接口中。

正好讲一下,我设计结构中各成员的作用吧。

size负责记录当前队列中存了几个元素。

capacity来记录题给k值,也就是队列空间的大小。

front是对头元素所在的下标。

tail就是队尾元素所在的下标。

myqueue就是维护数组队列空间的指针。

在create接口中,主要是对循环队列的初始化,先给这个队列的各个参数申请空间,这样即使出了接口也能使用这些变量,然后给capacity赋值,size归零,再给myqueue这个指针申请k个int的空间,把front和tail指向0地址。

接下来是enqueue接口,这个接口最重要的也不为过,先捋一下逻辑,正常来说,我们往数组插入数据的方式是通过下标来访问和修改值的。这里用tail来确定插入的位置,只有一点比较特殊,当tail走到了这个空间的尾部时,要把tail归零,不管0地址处是否有效,先指过去,按照传统来说,一个数组的下标越界之后,只访问不做任何修改是不会出问题的,但是人家网站就是不允许,所以一旦数组下标越界之后,统统放到0地址处。这里我们把数据放到tail下标所在空间,当队列满的时候,就不能再放了直接弹出false就可以了。每次放数据之后,tail向后移动一步,这里用if判断是否越界,一旦越界就拉回来。没了。

还有一个能讲的接口是dequeue,这个接口是唯一一个可以删除元素的节点,当然删除元素的方式有很多种,有释放空间,还有越过的方式,这里的数组早晚都可以free,所以我们直接越过数据就好了,在上面的接口是tail可能越界,这里是front可能越界,处理方式和上面的一样,一旦front越界就拉回0。。。。。。。什么是front越界呢?这个接口就是删除对头,我们的做法就是front++,front++的时候就会越界。

哦,差点忘了还有一个接口可能越界,但是有了方法的改良之后,大概率是不会越界的,就是接口rear,这个接口明明没用对数组进行操作,但是这个接口确实曾经越界过,因为之前的时候,放数据和处理front的大小这个顺序颠倒了。我一直想的是,假如有3个数据吧,我们放了123进去,这时front是0,tail是3,我们访问的时候只访问数组偏移量为2的地方就好了,但是人家网站不允许这种方式,哪怕tail只是指向了数组后面的空间,不使用也不行。所以要及时处理tail和front的大小。如果越界就拉回0处。

剩下的接口都比较简单了,也没出过问题,就不讲了。

完。。。。。

  • 41
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值