队列
队列(Queue)是一种线性数据结构。相关操作也比数组简单的多,但是队列与栈不同的是,队列只能从一段(队尾)添加元素,从另一端(队首)取出元素,即先进先出(FIFO)。
实现自己的队列
首先定义一个顶层接口:
/**
* @author Dongguabai
* @date 2018/9/13 9:16
*/
public interface Queue<E> {
/**
* 入队
* @param e
*/
void enqueue(E e);
/**
* 出队
* @return
*/
E dequeue();
/**
* 查看队首的元素
* @return
*/
E getFront();
/**
* 元素个数
* @return
*/
int getSize();
/**
* 是否为空
* @return
*/
boolean isEmpty();
}
基于动态数组实现的队列(关于动态数组可参看:https://blog.csdn.net/Dongguabai/article/details/82189286):
/**
* @author Dongguabai
* @date 2018/9/13 9:20
*/
@ToString
public class ArrayQueue<E> implements Queue<E>{
private MyArrayList<E> array;
public ArrayQueue(int capacity){
array = new MyArrayList<>(capacity);
}
public ArrayQueue(){
array = new MyArrayList<>();
}
@Override
public int getSize(){
return array.getSize();
}
@Override
public boolean isEmpty(){
return array.isEmpty();
}
public int getCapacity(){
return array.getCapacity();
}
@Override
public void enqueue(E e){
array.addLast(e);
}
@Override
public E dequeue(){
return array.removeFirst();
}
@Override
public E getFront(){
return array.getFirst();
}
}
从上面的代码也可以看出一个问题,在执行dequeue()方法时候,在队列的元素数量很大的时候是特别影响性能的,因为删除队首的元素,后面所有的元素都要向前挪一位,即时间复杂度为O(n)。
当a元素出队后,后面的b、c、d、e元素需要向前挪一位,同时size--。
解决办法就是我们可以使用front记录队首的元素位置,使用tail记录下次插入的元素的位置,这样每次出队列的时候,我们只需要维护front即可,即front++。
于是就出现了循环队列。
当队列为空的时候,front指向0,tail也指向0,即队列为空的时候front==tail:
这时候新增一个元素a,front不变,tail++,以此类推:
如果发生了出队操作,只需要fron++,tail不变:
以此类推,那为什么叫循环队列呢,因为如果此时队列元素情况如下:
如果再新增一个元素f,此时tail就不能++了,而此时队列前面还有空着的空间,这就可以成为循环队列,即数组可以看成一个环了。也就是说tail不能简单的执行++操作,应该是(tail+1)%capacity,在这里就是0,即此时tail应该指向索引为0的位置:
这样的话,再有新的元素进来了就将进入索引为0 的位置,tail继续加一:
但是这样问题就来了,之前说过当front==tail即表示队列为空,如果新的元素进入索引为1的位置,那么tail++,而此时front==tail,这样当front==tail即表示队列为空这个条件就不成立了,因为此时队列已满,这个不是我们所希望看到的。所以可以定义tail+1==front即表示队列中的数组已满,准确来说应该是(tail+1)%capacity==front,此时可以进行扩容处理,这样的话也就是说我们“有意识的浪费了一个空间”。
接下来实现自己的循环队列:
/**
* 循环队列
*
* @author Dongguabai
* @date 2018/9/13 10:27
*/
@ToString
public class LoopQueue<E> implements Queue<E> {
private E[] data;
private int front;
private int tail;
//也可以不需要size
private int size;
public LoopQueue(int capacity) {
//需要浪费一个空间
data = (E[]) new Object[capacity + 1];
front = 0;
tail = 0;
size = 0;
}
public LoopQueue() {
this(10);
}
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);
}
}
}
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) {
//浪费一个空间,即扩容就是实际容量*2
resize(getCapacity() * 2);
}
data[tail] = e;
tail = (tail + 1) % data.length;
size++;
}
@Override
public E dequeue() {
if (isEmpty()) {
throw new IllegalArgumentException("Cannot dequeue from an empty queue.");
}
E ret = data[front];
data[front] = null;
front = (front + 1) % data.length;
size--;
if (size == getCapacity() / 4 && getCapacity() / 2 != 0) {
resize(getCapacity() / 2);
}
return ret;
}
@Override
public E getFront() {
if (isEmpty()) {
throw new IllegalArgumentException("Queue is empty.");
}
return data[front];
}
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;
}