「这是我参与11月更文挑战的第5天,活动详情查看:2021最后一次更文挑战」
题目
设计你的循环队列实现。 循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。
循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。
你的实现应该支持如下操作:
MyCircularQueue(k): 构造器,设置队列长度为 k 。
Front: 从队首获取元素。如果队列为空,返回 -1 。
Rear: 获取队尾元素。如果队列为空,返回 -1 。
enQueue(value): 向循环队列插入一个元素。如果成功插入则返回真。
deQueue(): 从循环队列中删除一个元素。如果成功删除则返回真。
isEmpty(): 检查循环队列是否为空。
isFull(): 检查循环队列是否已满。
思路
由于是用js,我想的是用用数组模拟队列数据结构,增加的时候用push,删除的时候用shift,判断满员和为空则用数组长度和额定长度比较,取头尾元素则用下标去取。
歪打正着,这么去实现竟然通过了。
但是我们看了题解之后才明白,我这里其实是有问题的。 我增加用push没问题,但是删除用shift,那么每次删除,相当于都会将整个数组所有的元素向前移动一步,这其实在底层是会增加时间复杂度的。
我先把我的代码实现贴出来吧
代码
/**
* @param {number} k
*/
var MyCircularQueue = function(k) {
this.items = [];
this.length = k;
};
/**
* @param {number} value
* @return {boolean}
*/
MyCircularQueue.prototype.enQueue = function(value) {
if(!this.isFull()){
this.items.push(value);
return true;
}else{
return false;
}
};
/**
* @return {boolean}
*/
MyCircularQueue.prototype.deQueue = function() {
if(!this.isEmpty()){
this.items.shift();
return true;
}else{
return false;
}
};
/**
* @return {number}
*/
MyCircularQueue.prototype.Front = function() {
if(this.isEmpty()){
return -1
}else{
return this.items[0]
}
};
/**
* @return {number}
*/
MyCircularQueue.prototype.Rear = function() {
if(this.isEmpty()){
return -1
}else{
return this.items[this.items.length-1]
}
};
/**
* @return {boolean}
*/
MyCircularQueue.prototype.isEmpty = function() {
if(this.items.length == 0){
return true;
}else{
return false
}
};
/**
* @return {boolean}
*/
MyCircularQueue.prototype.isFull = function() {
if(this.length == this.items.length){
return true;
}else{
return false;
}
};
/**
* Your MyCircularQueue object will be instantiated and called as such:
* var obj = new MyCircularQueue(k)
* var param_1 = obj.enQueue(value)
* var param_2 = obj.deQueue()
* var param_3 = obj.Front()
* var param_4 = obj.Rear()
* var param_5 = obj.isEmpty()
* var param_6 = obj.isFull()
*/
题解
实际上顺序队列的实现,用数组结构来模拟没问题的,但其实需要有两个指针,一个指向头节点,一个指向尾节点。每次删除也只删除头节点,不用去移动所有的元素。
但这里也会引出一个问题。
假溢出
就是当队列满员之后,做几次删除操作,这时候队列实际上是没有满员的,但是你再增加元素的时候,就会报队列溢出 ,无法再往里增加了,因为最后一个位置有元素占着。正是因为有这个问题的存在,才会引入循环队列的概念。
按照题解的思路,代码实现如下
/**
* @param {number} k
*/
var MyCircularQueue = function(k) {
this.items = [];
this.capicity = k;
this.headIndex=0;
this.count = 0;
};
/**
* @param {number} value
* @return {boolean}
*/
MyCircularQueue.prototype.enQueue = function(value) {
if(!this.isFull()){
this.items[(this.headIndex + this.count)%this.capicity] = value;
this.count ++ ;
return true;
}else{
return false;
}
};
/**
* @return {boolean}
*/
MyCircularQueue.prototype.deQueue = function() {
if(!this.isEmpty()){
this.headIndex = (this.headIndex+1)%this.capicity;
this.count --;
return true;
}else{
return false;
}
};
/**
* @return {number}
*/
MyCircularQueue.prototype.Front = function() {
if(this.isEmpty()){
return -1
}else{
return this.items[this.headIndex]
}
};
/**
* @return {number}
*/
MyCircularQueue.prototype.Rear = function() {
if(this.isEmpty()){
return -1
}else{
return this.items[(this.headIndex + this.count-1)%this.capicity]
}
};
/**
* @return {boolean}
*/
MyCircularQueue.prototype.isEmpty = function() {
if(this.count == 0){
return true;
}else{
return false
}
};
/**
* @return {boolean}
*/
MyCircularQueue.prototype.isFull = function() {
if(this.count == this.capicity){
return true;
}else{
return false;
}
};
/**
* Your MyCircularQueue object will be instantiated and called as such:
* var obj = new MyCircularQueue(k)
* var param_1 = obj.enQueue(value)
* var param_2 = obj.deQueue()
* var param_3 = obj.Front()
* var param_4 = obj.Rear()
* var param_5 = obj.isEmpty()
* var param_6 = obj.isFull()
*/
题解二,使用链表来实现循环队列
思路:
定义头节点head,尾节点tail,链表长度count。count为0的时候,head和tail都指向同一个fake节点。 count为1的时候,head和tail同时指向真正的第一个节点。后续再新增的时候,tail指向新节点即可。 删除节点的时候,head的指针往后移动。读取front则返回头节点的值,读取rear则返回tail的值。
逻辑上没有问题。代码我后续再补上。
但是我有个问题,假设队列容量为5,我新增5个节点,再删除5个节点,然后再新增5个节点,其实内存中已经存在了10个节点,只不过我的head节点到tail节点只有5个节点而已,那么如果我重复删除5个再新增5个操作100次,内存中就又多了55个节点了。
这样是不是有内存溢出的问题?有没有大神帮忙解答一下。