循环队列是一种特殊的队列实现,在顺序队列的基础上进行了优化。通常,循环队列使用固定长度的数组来表示队列元素,头和尾指针挂钩形成循环的维度感知队列长度,并提高队列操作效率,因为这种结构需要的内存量比链表数据结构更小。
循环队列相对于普通队列的优势在于:
-
避免了数据移动所带来的性能损失。 顺序队列的主要瓶颈在于队首的位置不可变,出队后队列内其他元素必须整体向前移动一个位置,若处理大量数据时,则会带来较多时间复杂度为O(n)的操作。而在循环队列中可以频繁变换队首元素,支持随机访问(O(1)),避免了数据迁移的开销。
-
循环队列可以处理不定长的数据流 因为头和尾指针都指向空位置时,队列被看作已满,但实际上并不阻止任何新入队元素的到来。当队列长度不能再增加时,队首和队尾的位置互相套住。
我们通过力扣上的一道题练习循环队列
class MyCircularQueue {
public:
//初始化列表的初始化顺序是根据成员变量声明的先后顺序!
MyCircularQueue(int k)
: _front(0)
, _rear(0)
, _k(k)
, _a(new int[_k + 1])
{}
MyCircularQueue()
{
delete [] _a;
_a = nullptr;
}
bool enQueue(int value) {
if (isFull())
return false;
_a[_rear++] = value;
if (_rear == _k + 1)
_rear = 0;
return true;
}
bool deQueue() {
if (isEmpty())
return false;
_front++;
if (_front == _k + 1)
_front = 0;
return true;
}
int Front() {
if (isEmpty())
return -1;
return _a[_front];
}
int Rear() {
if (isEmpty())
return -1;
return _a[(_rear - 1) < 0 ? _k : (_rear - 1)];
}
bool isEmpty() {
return _front == _rear;
}
bool isFull() {
return (_rear + 1) % (_k + 1) == _front;
}
private:
int _front;
int _rear;
int _k;
int* _a;
};
这段代码实现了一个循环队列的基本操作,包括入队、出队、获取队头和队尾元素、检查队列是否为空或已满等方法。
循环队列是一种利用数组来实现队列的数据结构。同普通队列相比,循环队列提高了空间利用率,并解决了顺序队列中“假溢出”的问题。在循环队列中,所有元素都保存在一个连续的数组中,并且队列的两端都连接在数组的两端上。
类MyCircularQueue中定义了队列的常用变量_front,_rear,_k和_a。其中_front和_rear分别表示队列头和队列尾元素的位置。_k用于记录队列的最大容量。_a是一个指向动态数组的指针,包含的是队列中存储的元素。
实际上这里使用了数组的一部分来表示队列,为了避免队列在完全满时和空时的判断异常,被留空的一个位置。
入队enQueue()函数将元素加入到队列尾部。首先调用isFull()函数来检查队列是否已经满了。如果队列已满,则返回false,否则将给定的value放入数组中并移动_rear指针。当_rear达到数组末尾时,我们将其重置为0,以确保它一直指向队列的第一个位置。
出队deQueue()函数从队列头部删除元素。首先调用isEmpty()函数检查队列是否为空。如果队列非空,则将_front指针后移一位并返回true,否则返回false。当_front达到数组末尾时,我们也将其重置为0,以确保它一直指向队列的第一个位置。
Front()和Rear()函数分别获取队列头元素和队列尾元素。这两个方法首先检查队列是否为空,如果是则返回-1,否则返回_a数组中的相应元素。注意,由于是循环队列,尾指针_rear可能会回到数组开头。所以,在Rear()函数中需要特殊判断_rear是否在队列的第一个位置。
isEmpty()函数和isFull()函数分别检查队列是否为空或已满。队列为空的条件是_front等于_rear,队列已满的条件是(_rear + 1) % (_k + 1) == _front.
最后,类MyCircularQueue定义了析构函数来释放动态数组,并且通过初始化列表对数据成员进行初始化。