在上一篇文章的最后,我们提到了可以用循环队列来解决顺序队列只能使用一次的问题。在循环队列中,我们依然需要使用两个变量head(指向队列中的第一个元素,初始值为0)和tail(指向队列中最后一个元素的下一个位置,初始值为0)。
在顺序队列中,我们需要判断队空和队满的情况,同样在循环队列也需要判断队满和队空的情况。
上图中这个队列大小size为8,开始队列空时head和tail都指向下标0。依次添加元素a、b,此时如上图所示head位置不变,tail向后移指向下标为2的位置。继续添加元素c、d、e、f、g, 此时tail指向了最后一个位置。
数组实现的非循环队列中,队空的情况是head == tail
,循环队列中队空的情况也是head == tail
。非循环队列中队满的情况是tail == size
,循环队列中队满的情况是(tail + 1) % size == head
。
上图可以看出,当队列满时,tail指向的位置是没有存储元素的。如果我们想要最后一个位置也存储元素,那么tail需要继续向后移动一个位置,此时tail和head指向了同一个位置,与队列为空的情况是一样的条件head == tail,
所以我们约定牺牲一个存储空间来区分队空和队满的情况。
同样可以计算出队列中实际有效元素的个数为(tail + size - head) % size
。
下面我们使用代码来实现循环队列:
public class CircularQueue {
private Integer[] items;
private int size;
private int head = 0;
private int tail = 0;
public CircularQueue(int size) {
this.size = size;
items = new Integer[size];
}
/**
* 入队列
* @param item
* @return
*/
public boolean enqueue(int item) {
// 判断队列是否已满
if((tail + 1) % size == head) {
return false;
}
items[tail] = item;
tail = (tail + 1) % size;
return true;
}
/**
* 出队列
* @return
*/
public Integer dequeue() {
// 判断队列是否为空
if(head == tail) {
return null;
}
Integer item = items[head];
items[head] = null;
head = (head + 1) % size;
return item;
}
/**
* 打印队列中所有元素
*/
public void list() {
// 实际有效元素个数
int num = (tail + size - head) % size;
for(int i = head, len = head + num; i < len; i++) {
System.out.println(items[i%size]);
}
}
}