目录
思考
一,循环队列的初始化myCircularQueueCreate
二,判断是否为空myCircularQueueIsEmpty
三,判断队列是否满了bool myCircularQueueIsFull
四,队列的插入myCircularQueueEnQueue
五,队列的删除myCircularQueueDeQueue
六,队列取头元素和尾元素myCircularQueueFront // myCircularQueueRear
七,销毁队列myCircularQueueFree
本题要点
完整答案
一般我们入手的队列都是单项不循环的,所以我们会更倾向于选择用链表来实现,并且会选择单链表来简化队列先入先出的规则,但是这个题可能会颠覆你的认知。
我们再来看看chargpt3.5给出的循环队列的演示图(足够细致了):
思考
这个题要我们从0开始实现循环队列,那再用单链表就不太可能了,因为循环不了嘛。这个时候脑洞比较大的人比如我,就会想到很早做过的约瑟夫环,单项循环链表,带不带头无所谓的(带头反而浪费空间)。
方案一:用单项循环链表。
可不可行呢,是循环了,但是不知道你们有没有注意到函数的接口有一个Rear(获取队尾元素),单项的链表获取队尾元素是不是比较困难,还不如双向循环链表。
那可不可以换成用双向循环链表来弥补单项的不足呢!
方案二:用双向循环链表
可以是解决了取为难的问题,但是链表的结点是不是要一个一个创建再连接起来,连成k个,那链表实现的困难就是初始化困难很多,需要前后链接。再者,如果用链表来实现也不好判断到底这个队列是空的还是满的,因为循环了嘛。
方案三:用数组
在我们的众多讨论之下,选择了用数组来实现,优势1:数组的数据是连续的(参考顺序表),不需要去一个一个链接。优势2:处理起来更简单,增删改等操作都简单。
然后我们用类似之前双指针的思路,设置一个Front为数组的开头元素的序号,一个Rear为数组的结尾元素的序号,来判断数组是否空和满,但是这个Rear应该指向尾元素的下一个位置来。
因为,如果Rear不指向下一个元素那么当队列只有有一个元素的情况下Rear和Front的值是相同的,没有元素的情况下(初始化)也是相同的。就无法判断了。
我们选定了方法,也有了思路,那就来实现一下这个题:
一,循环队列的初始化myCircularQueueCreate
这里说一下为什么要多创造一个空间,主要是来好判断队列满了的情况,后面会解释的。
为什么obj也要创建一个空间呢,我们直接在实现顺序表时都没有这样操作呀,那是因为由于obj这个结构体是在创建函数之外创建的,这个函数的参数没有这个结构体,也就是说这个obj是创建在栈区了,出了函数就销毁了,malloc是重新创建在堆区的,只要程序不结束就不会消失,所以就要重新创建空间。所以如果直接写:
MyCircularQueue obj; //…… // return &obj;,这样obj作为函数里的临时变量,出函数就被销毁了,取地址再返回也是不行的!!!
二,判断是否为空myCircularQueueIsEmpty
红色的为空出来的空间,也就是多创建的空间,如图其实不难发现不管是有元素还是没有元素,rear循环了还是没有循环,rear+1都==front。
三,判断队列是否满了bool myCircularQueueIsFull
这里解释一下为什么要多开辟一个空间来解决伪充满的问题!!!
如图,当rear的值增大到6时由于要循环,所以rear%k = 0,为了让数组循环起来,%数组的大小是最好的选择,此时如果(rear + 1)% k = 1,这样永远都不会等于front,无法判断,如果rear不加上1直接去%,那么rear%k = 0 = front这样无法判断队列是空的还是满的。
如果不让rear指向尾元素的下一个,直接指向尾元素,这样的话,当rear等于5时,最后一个数据进入队列,这样手动将rear+1 = 6,(rear + 1)% k = 0 = front;但是如果在进数据时有数据被取出,那front就不是等于0,有可能等于1,2……这样,front和rear依然不相等呀。
所以,多创建一个空间就是最好的选择:由图可知:rear+1 == front,由于在不循环的情况时,rear+1一定不会大于k+1的所以%(k+1)也不会影响不循环的情况的,循环时为了防止rear一直加后越界,所以要%(k+1),这就是数组循环的条件!
四,队列的插入myCircularQueueEnQueue
插入时需要先判断队列是否为满的情况。
如果rear此时等于k时,如果前面没有足够的空位,那就需要考虑循环的问题就要%(k+1),不然就会越界访问。如果没有循环的问题那只需要插入在rear的位置就行,这个空格会一直出现在front的后面用以判断是否满了,空格就是多出来的空间。
五,队列的删除myCircularQueueDeQueue
删除则要考虑队列是否为空。剩下的思路和上面的插入差不多!!!
六,队列取头元素和尾元素myCircularQueueFront // myCircularQueueRear
取头尾元素都要先考虑队列是否为空,但是由于取头元素只要a[obj->Front],因为front没有什么变化,都对应着要取的数,那取尾会不会一样呢?
细心的你当然会发现肯定不是,如果没有循环是这样没错,但是如果rear循环就不一样了。
如图,如果rear循环,那正常rear-1就是尾,但是如果rear=0,那rear+1=-1,所以需要加上一个队列的大小(k+1)使其转正后再%(k+1),%(k+1)是为了防止rear越界,即使是不循环或者循环且rear不等于0的情况,加上一个队列的大小再%这个队列的大小值也是不变的。上图的算数式可以更好的证明!!!
七,销毁队列myCircularQueueFree
为什么不直接free(obj)呢?
分析obj的结构可得,因为obj这个结构体里面有a数组,如果直接free(obj),是只把obj所占的空间销毁了,但是里面的a数组的空间没有被销毁。
本题要点
本题主要是对于rear和front在移动时会不会越界的判断,和循环数组的处理方式(%(k+1)),rear-1越界的处理方式(+(k+1)),还需要更细致的判断能力和理解循环队列的循环结构。难度中等实至名归。
完整答案
这样本题就解析+解答结束。下面为完整的解答过程。
typedef struct {
int Front;
int Rear;
int* a;
int capacity;
} MyCircularQueue;
MyCircularQueue* myCircularQueueCreate(int k)
{
MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
obj->Front = 0;
obj->Rear = 0;
obj->capacity = k + 1;
obj->a = (int*)malloc(sizeof(int) * obj->capacity);
return obj;
}
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
if ((obj->Rear+1) % obj->capacity == obj->Front)
{
return false;
}
else
{
obj->a[obj->Rear] = value;
obj->Rear = obj->Rear+1;
obj->Rear = obj->Rear % obj->capacity;
return true;
}
}
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
if (obj->Rear == obj->Front) {
return false;
}
obj->Front = (obj->Front + 1) % obj->capacity;
return true;
}
int myCircularQueueFront(MyCircularQueue* obj) {
if (obj->Rear == obj->Front) {
return -1;
}
return obj->a[obj->Front];
}
int myCircularQueueRear(MyCircularQueue* obj)
{
if (obj->Rear == obj->Front)
{
return -1;
}
else
{
return obj->a[(obj->Rear-1 + obj->capacity) % obj->capacity];
}
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
return obj->Rear == obj->Front;
}
bool myCircularQueueIsFull(MyCircularQueue* obj) {
return (obj->Rear+1) % obj->capacity == obj->Front;
}
void myCircularQueueFree(MyCircularQueue* obj) {
free(obj->a);
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);文章来源:https://www.toymoban.com/news/detail-839108.html
*/文章来源地址https://www.toymoban.com/news/detail-839108.html
到了这里,关于力扣622:设计循环队列(中等)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!