什么是循环队列?
数组队列:https://blog.csdn.net/qq_42370146/article/details/82867649
前面我写到数组队列的一个很大的弊端,就是出队时删除头元素,相当于使数组后面的元素全部往前挪一位,即数组进行覆盖操作,其时间复杂度为O(n),而循环队列可以解决此弊端,使全部操作复杂度都变为O(1)。
假设一个数组队列已满,出队时删除头元素,我们不需要让数组元素往前移一位,只需要在入队时使元素插入到数组前端的空白处,此时虽然入队元素在数组前端,但仍作为队首,这样就可以优化出队时的时间复杂度,循环队列只是在数组队列的基础上进行优化。
--------------------------------------------------------------------------------------------------------------------------------------------------------
理解循环队列
front------>指向队首元素
tail-------->指向队尾元素的下一个索引位置
capacity----->数组大小
初始化队列为空,使front和tail都指向数组索引为0的位置,即当front=tail是队列为空。
入队操作:tail=(tail+1)%capacity
出队操作:front=(front+1)%capacity
队列为满:(tail+1)%capacity=front
至于为什么对数组大小求余是实现循环的关键,模拟一下即可以清楚其原理,循环的实现让我们有意识的在数组在浪费一个空间。
----------------------------------------------------------------------------------------------------------------------------------------------------------------
循环队列的实现
循环队列我们不再复用我们之前二次封装的动态数组类,而是重新从底层写起。
泛型类:LoopQueue
实现接口:Queue
成员变量(私有化):
E[ ] data,int front,int tail,int size
成员方法:
①LoopQueue() 构造方法,实例化对象时即指定数组大小
②int getCapacity() 得到队列大小
③boolean isEmpty() 判断队列是否为空
④int getSize() 得到队列元素个数
⑤void enqueue() 入队
⑥E dequeue() 出队
⑦void resize(int newCapacity) 动态扩容或缩容
⑧E getFront() 查看队首元素
⑨String toString() 重写Object类的方法
----------------------------------------------------------------------------------------------------------------------------------------------------------
public class LoopQueue<E> implements Queue<E> {
private E[] data;
private int front, tail, size;
// 构造方法,自定义队列长度
public LoopQueue(int capacity) {
data = (E[]) new Object[capacity + 1];
front = 0;
tail = 0;
size = 0;
}
// 构造方法,得到长度为10的队列
public LoopQueue() {
this(11);
}
// 得到队列大小
public int getCapacity() {
return data.length - 1;
}
@Override
// 判断队列是否为空
public boolean isEmpty() {
return front == tail;
}
@Override
// 得到队列中元素个数
public int getSize() {
return size;
}
@Override
// 入队
public void enqueue(E e) {
if ((tail + 1) % data.length == front)
resize(getCapacity() * 2);
data[tail] = e;
tail = (tail + 1) % data.length;
size++;
}
// 改变队列长度
private void resize(int newCapacity) {
E[] newData = (E[]) new Object[newCapacity + 1];
for (int i = 0; i < size; i++)
newData[i] = data[(i + front) % data.length];
data = newData;
front = 0;
tail = size;
}
@Override
// 查看队首元素
public E getFront() {
if (isEmpty())
throw new IllegalArgumentException("队列为空,操作失败!");
return data[front];
}
@Override
// 出队
public E dequeue() {
if (isEmpty())
throw new IllegalArgumentException("队列为空,操作失败!");
E temp = data[front];
data[front] = null;
front = (front + 1) % data.length;
size--;
if (size == getCapacity() / 4 && getCapacity() / 2 != 0)
resize(getCapacity() / 2);
return temp;
}
@Override
// 重写toString()方法
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(String.format("Queue:size=%d,capacity=%d\n", size, getCapacity() - 1));
sb.append(" front [");
for (int i = front; i != tail; i = (i + 1) % data.length) {
sb.append(data[i]);
if ((i + 1) % data.length != tail)
sb.append(", ");
}
sb.append("] tail");
return sb.toString();
}
}
虽然循环队列比数组队列复杂些,但是这种算法优化无疑是值得的,在出队的执行效率上相差了上百倍,不得不感叹算法之妙,队列应用很广,现在实现的只是队列中很基础的形式,在以后深入会学习更多队列内容,例如经常在比赛里用到的广度优先算法,就是二叉树+队列的应用,所以学习数据结构会对算法的本质有更深层次的理解,ojbk!!