循环队列
循环队列概念
所谓的循环队列,其实还是一个队列,从外部调用的接口和实际的功能上看和普通的队列完全没有区别,而之所以它叫循环队列而不是队列,是因为它用于实现的底层逻辑和一般队列不同,一般队列底层采用双向链表,而循环队列底层采用索引不断循环的数组实现。
循环队列操作图示
建立循环队列,势必要先建立一个数组:
E elements=(E[]) new Object[8];//其中E表示泛型
假如向队列中入队11,22,33,44这四个元素,则其存储结构如图所示。
此时若将元素55入队列,则直接在尾元素后入队列:
倘若要出队列,则稍稍复杂一些,将队头元素清空,并将front的指向后移:
假设在此基础上出队3次,并入队66,77,88,则结果如下图所示:
若在数组没有更大的索引位置可以使用时,还要继续入队99,则采用索引循环的方法,即放到左边空余的数组空间。此时入队,若元素从0开始放置(即front从0开始),入队元素应该放置在索引为4即size的位置(因为已有4个元素,分别放置了0,1,2,3的位置),而此时由于front的偏移,入队元素放置的位置也需要进行偏移,即size+front=8。而8明显超出了数组可以用的索引,此时将8对数组大小取模,放置左侧空余的数组空间(循环)。
再次入队100,101,102:
此时队列已经装满,若还要继续入队,则需要进行扩容(新建一个更大的数组,将数组从0开始迁移过去,并修改原数组引用对象指向新数组):
private void ensureCapacity(int newSize){
int oldCapacity=elements.length;
//如果容量可以承受则不扩容
if(newSize<=oldCapacity)return;
//否则扩容1.5
newSize=oldCapacity+(oldCapacity>>1);
E[] newElements= (E[]) new Object[newSize];
//数据迁移
for(int i=0;i<size;i++)
newElements[i]=elements[index(i)];
elements=newElements;
front=0;//指针归零
System.out.println(oldCapacity+"扩容为"+newSize);
}
/**
* 简化取模的索引映射
* @param index
* @return
*/
private int index(int index){
//避免取模运算(效率低下)
index+=front;
return index-(index>=elements.length?elements.length:0);
}
此时扩容后的结果将为:
然后再入队:
以上就是循环队列的操作步骤。
具体实现
public class CircleQueue<E>{
private int front;//指向头节点
private int size;
private E[] elements;
//默认队列大小
private static final int DEFAULT_CAPACITY=10;
public CircleQueue(){
this(DEFAULT_CAPACITY);
}
public CircleQueue(int capacity){
capacity=capacity>DEFAULT_CAPACITY?capacity:DEFAULT_CAPACITY;
elements= (E[]) new Object[capacity];
}
/**
* 获取队列大小
* @return
*/
public int size(){
return size;
}
/**
* 判断数组是否为空
* @return
*/
public boolean isEmpty(){
return size==0;
}
/**
* 清空队列
*/
public void clear(){
//清空所有引用,等待jvm进行垃圾回收
for(int i=0;i<size;i++)
elements[index(i)]=null;
size=0;
//还原指针
front=0;
}
/**
* 入队
*/
// 0 0 1 2 3
public void offer(E element){
ensureCapacity(size+1);
elements[index(size)]=element;//末尾入队
size++;
}
private void ensureCapacity(int newSize){
int oldCapacity=elements.length;
//如果容量可以承受
if(newSize<=oldCapacity)return;
//否则扩容1.5
newSize=oldCapacity+(oldCapacity>>1);
E[] newElements= (E[]) new Object[newSize];
//数据迁移
for(int i=0;i<size;i++)
newElements[i]=elements[index(i)];
elements=newElements;
front=0;//指针归零
System.out.println(oldCapacity+"扩容为"+newSize);
}
/**
* 简化取模的索引映射
* @param index
* @return
*/
private int index(int index){
//避免取模运算(效率低下)
index+=front;
return index-(index>=elements.length?elements.length:0);
}
/**
* 出队
* @return
*/
public E poll(){
E frontElement=elements[front];
elements[front]=null;
front=index(1);//更新指针到下一个元素
size--;
return frontElement;
}
/**
* 获取队头元素
* @return
*/
public E peek(){
return elements[front];
}
@Override
public String toString() {
return "CircleQueue{" +
"front=" + front +
", size=" + size +
", elements=" + Arrays.toString(elements) +
'}';
}
}
循环双端队列
循环双端队列概念
循环双端队列其实就是一个双端队列,即可以在队头和队尾都进行入队或者出队操作的队列。它与循环队列相比,也只是增加了在队尾进行出队操作的接口offerRear和在队头进行入队操作的pollFront,其次由于在队头入队需要获取到原始索引为-1的映射索引(即头节点之前的节点,头节点原始索引为0),还需要对提供索引映射的index(int index)方法进行修改。
具体实现
public class CircleDeque<E> {
private int front;//指向头节点
private int size;
private E[] elements;
//默认队列大小
private static final int DEFAULT_CAPACITY=10;
public CircleDeque(){
this(DEFAULT_CAPACITY);
}
public CircleDeque(int capacity){
capacity=capacity>DEFAULT_CAPACITY?capacity:DEFAULT_CAPACITY;
elements= (E[]) new Object[capacity];
}
/**
* 获取队列大小
* @return
*/
public int size(){
return size;
}
/**
* 判断数组是否为空
* @return
*/
public boolean isEmpty(){
return size==0;
}
/**
* 清空队列
*/
public void clear(){
//清空所有引用,等待jvm进行垃圾回收
for(int i=0;i<size;i++)
elements[index(i)]=null;
size=0;
//还原指针
front=0;
}
/**
* 从队尾入队
*/
// 0 0 1 2 3
public void offerRear(E element){
ensureCapacity(size+1);
elements[index(size)]=element;//末尾入队
size++;
}
/**
* 从队头入队
*/
public void offerFront(E element){
ensureCapacity(size+1);
int newFront=size==0?0:index(-1);//若放放入的是第一个元素,直接放在0处
elements[front=newFront]=element;//队头入队并跟新front
size++;
}
private void ensureCapacity(int newSize){
int oldCapacity=elements.length;
//如果容量可以承受
if(newSize<=oldCapacity)return;
//否则扩容1.5
newSize=oldCapacity+(oldCapacity>>1);
E[] newElements= (E[]) new Object[newSize];
//数据迁移
for(int i=0;i<size;i++)
newElements[i]=elements[index(i)];
elements=newElements;
front=0;//指针归零
System.out.println(oldCapacity+"扩容为"+newSize);
}
/**
* 简化取模的索引映射
* @param index
* @return
*/
private int index(int index){
//防止index为-1
//避免取模操作
index+=front;
if(index<0)
return index+elements.length;
return index-(index>=elements.length?elements.length:0);
}
/**
* 从队头出队
* @return
*/
public E pollFront(){
E frontElement=elements[front];
elements[front]=null;
front=index(1);//更新指针到下一个元素
size--;
return frontElement;
}
/**
* 从队尾出队
* @return
*/
public E pollRear(){
int rear=index(size-1);
E frontElement=elements[rear];
elements[rear]=null;//尾部出队
size--;
return frontElement;
}
/**
* 获取队头元素
* @return
*/
public E peekFront(){
return elements[front];
}
/**
* 获取队尾元素
* @return
*/
public E peekRear(){
return elements[index(size-1)];
}
@Override
public String toString() {
return "CircleDeque{" +
"front=" + front +
", size=" + size +
", elements=" + Arrays.toString(elements) +
'}';
}
}