循环队列 是一种线性数据结构,其操作表现基于 FIFO
(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为 环形缓冲器。
循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。
为什么使用循环队列?
这里我们先对 队列 的简单实现进行简单展示,队列应支持两种操作:入队 和 出队 。
class MyQueue {
// store elements
private List<Integer> data;
// a pointer to indicate the start position
private int p_start;
public MyQueue() {
data = new ArrayList<Integer>();
p_start = 0;
}
/** Insert an element into the queue. Return true if the operation is successful. */
public boolean enQueue(int x) {
data.add(x);
return true;
};
/** Delete an element from the queue. Return true if the operation is successful. */
public boolean deQueue() {
if (isEmpty() == true) {
return false;
}
p_start++;
return true;
}
/** Get the front item from the queue. */
public int Front() {
return data.get(p_start);
}
/** Checks whether the queue is empty or not. */
public boolean isEmpty() {
return p_start >= data.size();
}
}
缺点
上面的实现很简单,但在某些情况下效率很低。 随着起始指针的移动,浪费了越来越多的空间。 当我们有空间限制时,这将是难以接受的。
让我们考虑一种情况,即我们只能分配一个最大长度为 5 的数组。当我们只添加少于 5 个元素时,我们的解决方案很有效。 例如,如果我们只调用入队函数四次后还想要将元素 10 入队,那么我们可以成功。
但是我们不能接受更多的入队请求,这是合理的,因为现在队列已经满了。但是如果我们将一个元素出队呢?
实际上,在这种情况下,我们应该能够再接受一个元素。
循环队列 的需求迫在眉睫。
设计思想
这里直接复制了 liweiwei1419 精彩的 题解 :
1、定义循环变量 front 和 rear 。一直保持这个定义,到底是先赋值还是先移动指针就很容易想清楚了。
front
:指向队列头部第 1
个有效数据的位置;
rear
:指向队列尾部(即最后 1
个有效数据)的下一个位置,即下一个从队尾入队元素的位置。
(说明:这个定义是依据“动态数组”的定义模仿而来。)
2、为了避免“队列为空”和“队列为满”的判别条件冲突,我们有意浪费了一个位置。
浪费一个位置是指:循环数组中任何时刻一定至少有一个位置不存放有效元素。
判别队列为空的条件是:front == rear
;
判别队列为满的条件是:(rear + 1) % capacity == front
;。可以这样理解,当 rear
循环到数组的前面,要从后面追上 front
,还差一格的时候,判定队列为满。
3、因为有循环的出现,要特别注意处理数组下标可能越界的情况。指针后移的时候,索引 + 1,并且要注意取模。
例题
本文的写作目的就是为了 再次手写一边算法练手。
622.设计循环队列
- 难度:
Medium
参考上述设计思路,很容易编写代码,需要注意的是取模代码的编写相对容易出问题。
实现代码:
class MyCircularQueue {
private int front;
private int tail;
private int capacity;
private int[] arr;
public MyCircularQueue(int k) {
this.capacity = k + 1;
this.arr = new int[capacity];
this.front = 0;
this.tail = 0;
}
public boolean enQueue(int value) {
if (isFull()) return false;
arr[tail] = value;
tail = (tail + 1) % capacity;
return true;
}
public boolean deQueue() {
if (isEmpty()) return false;
front = (front + 1) % capacity;
return true;
}
public int Front() {
if (isEmpty()) return -1;
return arr[front];
}
public int Rear() {
if (isEmpty()) return -1;
return arr[(tail - 1 + capacity) % capacity];
}
public boolean isEmpty() {
return front == tail;
}
public boolean isFull() {
return (tail + 1) % capacity == front;
}
}
641. 设计循环双端队列
- 难度:
Medium
和上题基本相似,除了多几个取模运算,几乎没什么变化。
class MyCircularDeque {
private int front;
private int tail;
private int capacity;
private int[] arr;
public MyCircularDeque(int k) {
this.capacity = k + 1;
this.arr = new int[capacity];
this.front = 0;
this.tail = 0;
}
public boolean insertFront(int value) {
if (isFull()) return false;
front = (front - 1 + capacity) % capacity;
arr[front] = value;
return true;
}
public boolean insertLast(int value) {
if (isFull()) return false;
arr[tail] = value;
tail = (tail + 1) % capacity;
return true;
}
public boolean deleteFront() {
if (isEmpty()) return false;
front = (front + 1) % capacity;
return true;
}
public boolean deleteLast() {
if (isEmpty()) return false;
tail = (tail - 1 + capacity) % capacity;
return true;
}
public int getFront() {
if (isEmpty()) return -1;
return arr[front];
}
public int getRear() {
if (isEmpty()) return -1;
return arr[(tail - 1 + capacity) % capacity];
}
public boolean isEmpty() {
return front == tail;
}
public boolean isFull() {
return (tail + 1) % capacity == front;
}
}
参考 & 感谢
文章绝大部分内容节选自LeetCode
:
https://leetcode-cn.com/problems/design-circular-queue
https://leetcode-cn.com/problems/design-circular-deque
感谢 liweiwei1419 提供的精彩的 题解 。
关于我
Hello,我是 却把清梅嗅 ,如果您觉得文章对您有价值,欢迎 ❤️,也欢迎关注我的 博客 或者 GitHub。
如果您觉得文章还差了那么点东西,也请通过关注督促我写出更好的文章——万一哪天我进步了呢?
转载:https://blog.csdn.net/mq2553299/article/details/104057271