什么是队列?
队列是一种线性数据结构,要理解它,其实非常简单,举个例子。
假如高速公路上有一条隧道,所有通过隧道的车辆只允许从隧道的入口驶入,从隧道出口驶出,不允许逆行。因此,要想让车辆驶出隧道,只能按照车辆的驶入顺序,先驶入的车辆先驶出,后驶入的车辆后驶出,任何车辆都无法跳过它前面的车辆提前驶出。
队列是一种线性数据结构,它不同于栈,队列中的元素只能先进先出(First In First Out,简称FIFO),队列的出口端叫队首,队列的入口段叫队尾。
队列的实现
1、入队(enqueue)
入队就是把新的元素放入队列中,只允许从队尾放入元素,并且新元素将会成为下一个新的队尾。
2、出队(dequeue)
出队就是把队首元素移除队列,出队元素的后一个元素成为新队首。
整体代码如下:
/**
* 描述:队列所需的方法。
*
* Create By ZhangBiao
* 2020/5/10
*/
public interface Queue<E> {
/**
* 入队操作
*
* @param e
*/
void enqueue(E e);
/**
* 出队操作
*
* @return
*/
E dequeue();
/**
* 获取队首
*
* @return
*/
E getFront();
/**
* 获取队列元素个数
*
* @return
*/
int getSize();
/**
* 判断队列是否为空
*
* @return
*/
boolean isEmpty();
}
/**
* 描述:基于动态数组实现队列。
* <p>
* Create By ZhangBiao
* 2020/5/10
*/
public class ArrayQueue<E> implements Queue<E> {
private Array<E> array;
public ArrayQueue() {
array = new Array<>();
}
public ArrayQueue(int capacity) {
array = new Array<>(capacity);
}
@Override
public void enqueue(E e) {
this.array.addLast(e);
}
@Override
public E dequeue() {
return this.array.removeFirst();
}
@Override
public E getFront() {
return this.array.getFirst();
}
@Override
public int getSize() {
return this.array.getSize();
}
@Override
public boolean isEmpty() {
return this.array.isEmpty();
}
/**
* 获取队列容量大小。
*
* @return
*/
public int getCapacity() {
return this.array.getCapacity();
}
@Override
public String toString() {
StringBuilder result = new StringBuilder();
result.append("Queue: ");
result.append("front [");
for (int i = 0; i < array.getSize(); i++) {
result.append(array.get(i));
if (i != array.getSize() - 1) {
result.append(", ");
}
}
result.append("] tail");
return result.toString();
}
}
循环队列的实现
注意:如果队列不断的出队,那么左边的内存空间就会浪费,这时就可以使用循环队列来维持队列容量。
循环队列是什么呢?
假设我们有一个这样的队列。如下:
这时执行一个入队操作,在数组不扩容的前提下,如何让新元素入队并确定新的队尾位置呢?可以利用已出队元素留下的空间,让队尾指针重新指向数组的首位。
这样一来,整个队列的元素就循环起来了。在物理存储上,队尾的位置可以在队首之前,当再有元素入队时,将其放入数组的首位,队尾指针后移即可。
一直到(tail + 1)% 数组长度 == front 时,代表队列已满。注意:队尾指针指向的位置永远空出一位,所以在循环队列中会浪费一个空间。
这就是所谓的循环队列。
整体代码如下:
/**
* 描述:循环队列。
* <p>
* Create By ZhangBiao
* 2020/5/10
*/
public class LoopQueue<E> implements Queue<E> {
private E[] data;
private int front;
private int tail;
private int size;
public LoopQueue() {
this(10);
}
public LoopQueue(int capacity) {
data = (E[]) new Object[capacity + 1];
front = 0;
tail = 0;
size = 0;
}
@Override
public void enqueue(E e) {
if ((tail + 1) % data.length == front) {
resize(getCapacity() * 2);
}
data[tail] = e;
tail = (tail + 1) % data.length;
size++;
}
/**
* 扩容
*
* @param newCapactiy
*/
private void resize(int newCapactiy) {
E[] newData = (E[]) new Object[newCapactiy + 1];
for (int i = 0; i < size; i++) {
newData[i] = data[(front + i) % data.length];
}
data = newData;
front = 0;
tail = 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];
}
@Override
public int getSize() {
return size;
}
@Override
public boolean isEmpty() {
return front == tail;
}
public int getCapacity() {
return data.length - 1;
}
@Override
public String toString() {
StringBuilder result = new StringBuilder();
result.append(String.format("Queue: size = %d , capacity = %d
", size, getCapacity()));
result.append("front [");
for (int i = front; i != tail; i = (i + 1) % data.length) {
result.append(data[i]);
if ((i + 1) % data.length != tail) {
result.append(", ");
}
}
result.append("] tail");
return result.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);
}
}
}
}