背景
循环队列出队和入队的均摊复杂度均为O(1)级别,性能比普通队列更高
普通队列的入队复杂度为O(1),但出队复杂度为O(n),因为当出队时,后面的数组有个前移一位的过程.
实现原理
语言描述
实现原理其实也比较简单,就是数组维护队首front和队尾tail索引.当出队时,无需移动数据位置.当入队时,到达数组底部后,通过取余回到数组前端.索引类似在一个数组环上移动,这也是循环一名的由来.
当front和tail相同时,即代表队列为空.当tail的下一个为front时,即代表队列已满,需要扩容.
代码实现
package xyz.yq.ds.queue;
/**
* 循环队列
* <br> 减少出队时的时间复杂度: 传统队列出队的时间复杂度为O(n),中间会有一个所有元素前移的过程
* <br> 设计理念: 类似时间环,当tail和head相等时,队列为空;当tail-1=head时,队列为满
*
* @author yi qiang
* @date 2022/5/15 22:15
*/
@SuppressWarnings("all")
public class LoopQueue<E> implements Queue<E> {
/**
* 数组
*/
E[] data;
private int front = 0;
private int tail = 0;
private int size = 0;
/**
* 判断队列是否已满的时候会浪费一个空间,故需要+1
* <br> 比如用户希望容量是10个,那实际要是11个才能满足需要
*
* @param capacity 容量
*/
public LoopQueue(int capacity) {
data = (E[]) new Object[capacity + 1];
}
public LoopQueue() {
this(10);
}
public int getCapacity() {
//因为会浪费掉一个空间,实际容纳的数据量要-1
return data.length - 1;
}
@Override
public int getSize() {
return size;
}
/**
* 当head==tail时,说明队列为空
*
* @return 是否为空队列
*/
@Override
public boolean isEmpty() {
return tail == front;
}
/**
* 当tail只与head差一个节点时,队列已满
*
* @return 队列是否已满
*/
public boolean isFull() {
return (tail + 1) % data.length == front;
}
/**
* 入队
*
* @param e 元素
*/
@Override
public void enqueue(E e) {
if (isFull()) {
//扩容为实际能承载的数据的2倍
resize(getCapacity() * 2);
}
//入队,将尾节点先后移动一位即可.由于可能超出队列长度,故取队列长度的余数,让内存可以循环利用
this.data[tail] = e;
tail = (tail + 1) % data.length;
size++;
}
/**
* 出队
*
* @return 队首的值
*/
@Override
public E dequeue() {
if (isEmpty()) {
throw new IllegalArgumentException("Cannot dequeue from a empty queue.");
}
E ret = data[front];
data[front] = null;
size--;
front++;
//出队后可能不需要那么大空间了,此时进行缩容操作
if (size == getCapacity() / 4 && getCapacity() / 2 != 0) {
resize(getCapacity() / 2);
}
return ret;
}
/**
* 扩容与缩容操作
*
* @param newCapacity 新容量
*/
private void resize(int newCapacity) {
//因为会浪费掉一个空间,实际容纳的数据量要-1
E[] newData = (E[]) new Object[newCapacity + 1];
//从0到size-1处即可
for (int i = 0; i < size; i++) {
//新数组数据是从0开始
newData[i] = data[(i + front) % data.length];
}
data = newData;
front = 0;
tail = size;
}
@Override
public E getFront() {
if (isEmpty()) {
throw new IllegalArgumentException("Cannot dequeue from a empty queue.");
}
return data[front];
}
@Override
public String toString() {
StringBuilder res = new StringBuilder();
res.append(String.format("Queue: size=%d , capacity=%d \n", size, getCapacity()));
res.append("front [");
//语义方式
// for (int i = front; i != tail; i = (i + 1) % data.length) {
// res.append(data[i]);
// if ((i + 1) % data.length != tail) {
// res.append(",");
// }
// }
//偏移量方式
for (int i = 0; i < size; i++) {
res.append(data[(i + front) % data.length]);
if (i != size - 1) {
res.append(",");
}
}
res.append("] tail");
return res.toString();
}
public static void main(String[] args) {
LoopQueue<Integer> queue = new LoopQueue<>();
for (int i = 0; i < 10; i++) {
queue.enqueue(i);
System.out.println(queue);
if (i % 3 == 2) {
queue.dequeue();
System.out.println(queue);
}
}
}
}