1. 引入循环队列的原因:普通队列的出队时间复杂度O(n),不是按平均算的,因为每次出队都是O(n)。因此我们为了降低出队的时间复杂度,我们可以定义一个循环队列来实现队列的操作。
2. 循环队列的特点:即当队尾或队头指针到达尾部时,如需后移可重新指向表头。如图:其中front表示队头指针,rear表示队尾指针。
注意:我们在定义循环队列时会预留出一个空的位置,是尾指针rear始终指向这个空的空间,这样就会避免队满和队空条件一样的问题。避免了二异性。
3. 循环队列的接口:其中只定义一些循环队列操作的方法。
public interface LoopQueue<E> extends Iterable<E>{
//相队尾添加元素
public void offer(E element);
//删除队首元素
public E poll();
//查看队首元素
public E element();
//判断队列是否为空
public boolean isEmpty();
//清空队列中的所有元素
public void clear();
//获取队列中有效元素的个数
public int size();
}
4. 循环队列接口的实现类:该循环队列的实现思想也是动态数组,但是由于操作元素的特殊性,并不能直接由ArrayList或ArrayQueue实现,所以我们要从头开始定义ArrayLoopQueue。
//实现循环队列
public class ArrayLoopQueue<E> implements LoopQueue<E> {
/*
循环队列 我们要留出一个空的空间 使尾指针始终指向这个空的空间 这样对判断队满和队空时条件就不会相同 避免了二异性
元素存储就像时一个环一样
当(rear + 1)%data.length == front时 表示队满
当rear == front(rear和front同时指向空的那块空空间)时 表示队空
*/
//存放数据的容器
private E[] data;
//队首指针 每次更新为:(front+1)%data.length
private int front;
//队尾指针 每次更新为:(rear+1)%data.length
private int rear;
//有效元素的个数
private int size;
//容器默认容量
private static int DEFAULT_CAPACITY = 10;
//初始化data front rear size
public ArrayLoopQueue(){
data = (E[]) new Object[DEFAULT_CAPACITY + 1];
front = 0;
rear = 0;
size = 0;
}
//向队尾添加元素
//当队首指针和队尾指针关系为: (rear + 1) % data.length == front时表示队满了
@Override
public void offer(E element) {
if((rear + 1) % data.length == front){ //判断循环队列是否已满
resize(data.length * 2 - 1); //扩容循环队列
}
data[rear] = element;
rear = (rear + 1) % data.length;
size++;
}
//删除队首元素
@Override
public E poll() {
if(isEmpty()){ //先判空
throw new IllegalArgumentException("queue is not null");
}
E ele = data[front]; //先获取队首元素
front = (front + 1) % data.length; //再更新队首指针
size--;
//判断是否满足数组缩容条件 若满足就进行缩容
if(size <= data.length - 1 && data.length - 1 > DEFAULT_CAPACITY){
resize(data.length / 2 + 1);
}
return ele;
}
//对数组进行缩容 / 扩容操作
private void resize(int newLength) {
E[] newData = (E[]) new Object[newLength];
int index = 0;
for(int i = front; i != rear; i = (i + 1) % data.length){
newData[index++] = data[i];
}
data = newData; //将新数组赋给原数组
front = 0; //更新队首指针 为新数组的队首0
rear = index; //更新队尾指针 为新数组的队尾index
}
//查看队首元素
@Override
public E element() {
if(isEmpty()){ //先判空
throw new IllegalArgumentException("queue is not null");
}
//获取队首元素
E ele = data[front];
return ele;
}
//判断队列是否为空
@Override
public boolean isEmpty() {
return front == rear;
}
//清空队列中的所有元素
@Override
public void clear() {
data = (E[]) new Object[DEFAULT_CAPACITY + 1];
front = 0;
rear = 0;
size = 0;
}
//获取队列中有效元素的个数
@Override
public int size() {
return size;
}
//判断两个循环队列是否相等
@Override
public boolean equals(Object o) {
//先判空
if(o == null){
return false;
}
//判断是否是自己
if(this == o){
return true;
}
//怕判断是否是同一类型
if(o instanceof ArrayLoopQueue){
ArrayLoopQueue<E> other = (ArrayLoopQueue<E>) o;
//判断有效元素的个数是否相等
if(this.size != other.size){
return false;
}
//如果有效元素格式相等 就比较内容是否相等
int i = this.front; //表示当前循环队列的索引
int j = other.front; //表示指定循环队列的索引
while (i != rear){
//判断当前循环队列的当前索引位置的值 与 指定循环队列的当前索引的值 是否相等
if(!data[i].equals(other.data[j])){
return false;
}
i = (i + 1) % data.length; //更新当前循环队列的索引
j = (j + 1) % other.data.length; //更新指定循环队列的索引
}
return true;
}
return false;
}
//规定循环队列输出的格式
@Override
public String toString() {
StringBuilder str = new StringBuilder();
str.append("ArrayLoopQueue: " + size + "/" + (data.length - 1) + "[");
if(isEmpty()){
str.append(']');
}
for(int i = front; i != rear; i = (i + 1) % data.length){
str.append(data[i]);
if((i + 1) % data.length == rear){
str.append(']');
}else{
str.append(',');
str.append(' ');
}
}
return str.toString();
}
//获取当前这个数据结构/容器 的 迭代器
//通过迭代器对象 更方便挨个取出每一个元素
//同时 实现了Iterable 可以让当前的数据结构/容器 被foreach循环遍历
@Override
public Iterator<E> iterator() {
return new ArrayLoopQueueIterator();
}
//循环队列的迭代器
class ArrayLoopQueueIterator implements Iterator<E>{
private int cur = front; //表示游标 从循环队列的队首指针开始
@Override
public boolean hasNext() {
return cur != rear; //当cur不等于rear时 表示可以继续迭代 有下一个元素
}
@Override
public E next() {
E ele = data[cur]; //获取当前游标cur的元素
cur = (cur + 1) % data.length; //更新游标的值
return ele;
}
}
}