像栈一样,队列也是一种线性表。它允许在表的一端插入数据,在另一端删除元素。插入元素的这一端称之为队尾。删除元素的这一端我们称之为队首。队列的结构如下图所示。
队列的特性:
在队尾插入元素,在队首删除元素。
FIFO(先进先出),就向排队取票一样。
顺序队列的实现
使用上一篇文章的动态数组实现了队列,代码非常简单,不多讲了。
public class ArrayQueue {
private Array<E> data;
public ArrayQueue(){
this.data = new DynamicArray();
}
public boolean isEmpty() {
return data.isEmpty();
}
public int size() {
return data.size();
}
public void enqueue(E e) {
data.add(e);
}
public E dequeue() {
return data.remove(0);
}
public E head() {
return data.get(0);
}
@Override
public String toString() {
return "Head " + data.toString() + " Tail";
}
}
循环队列
从顺序队列的出队操作可以看出,当删除一个元素时,队列中的元素都要向前移动一个位置。为了解决这个问题,这里介绍下循环队列。
我们可以将队列看成一个首位相连的环。当一个元素从队列删除后,新空余出来的空间可以作为队列的尾部。如图下所示。
在编写代码之前,我们先考虑两个问题。队列的数组下标怎么计算?
如果是在普通的队列中,那么插入元素,直接将tail + 1即可。可是在循环队中,元素插入的位置可能是原来队首的位置,如上图中的I,这样如果直接用tail + 1就会造成越界。所以我们计算数组下标的时候要进行取模。使用( tail + 1 ) % n。同理,删除元素也是类似。
下标的问题解决了,那么还有个问题,如何判断队列是否为空,队列是否为满呢?
从上面的图可以看出当tail == head的时候,这个是否队列是即可以为空,又可以为满。那么我们不妨将队列最后一个元素的空气预留出来。这样的话当tail == head的时候,队列为空。当队列的tail + 1 == head,我们就认为队列已经满了,因为是循环队列,所以计算tail + 1 == head应该算上偏移量即 ( tail + 1 ) % n = head % n。
在下文的代码中,简单的用数组实现了循环队列。tail == head认为队列为空,( tail + 1 ) % n == head % n认为队列已满。另外队列支持扩容和缩容,扩容操作和缩容操作上一篇文章的动态数组一致,详见下文。
public class LoopQueue<E> {
private E[] data;
private int size;
private int tail;
private int head;
public LoopQueue(){
this(8);
}
public LoopQueue(int capcaity) {
this.data = (E[]) new Object[capcaity + 1];
this.head = 0;
this.tail = 0;
this.size = 0;
}
public int size() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
public void enqueue(E e) {
if((tail + 1) % data.length == head % data.length){
resize(data.length * 2);
}
data[tail] = e;
tail = (tail + 1)% data.length;
size++;
}
public E dequeue() {
if(tail == head){
return null;
}
E e = data[head];
head = (head + 1)% data.length;
size--;
if(size == data.length / 4 && data.length / 2 != 0){
resize(data.length / 2);
}
return e;
}
public E head() {
return data[head];
}
private void resize(int newCapcaity){
E[] newData = (E[])new Object[newCapcaity + 1];
for(int i = 0 ; i < size ; i ++) {
newData[i] = data[(i + head) % data.length];
}
head = 0;
tail = size;
data = newData;
}
@Override
public String toString() {
StringBuilder sBuilder = new StringBuilder();
sBuilder.append("Head ");
for(int i = 0 ; i < size ; i ++) {
sBuilder.append(data[(i + head) % data.length]).append(",");
}
int lastIndex = sBuilder.lastIndexOf(",");
if(lastIndex != -1){
sBuilder.deleteCharAt(lastIndex);
}
sBuilder.append(" Tail");
return sBuilder.toString();
}