如何用数组优雅且正确地实现一个队列呢
**
本文采用我思考时的思路,分别展示了两版代码,第一版存在问题,第二版基本无误(如果有错欢迎指正)
第一版
public class ArrayQueue<AnyType> {
private AnyType[] dataElem; //存放元素的基础数组
private int size; //记录下元素的个数
private static final int DEFAULT_CAPACITY = 10; //数组默认容量
private int front; //永远指向队首元素(下标)
private int rear; //永远指向队尾元素(下标)
public ArrayQueue(){
ensureCapacity(DEFAULT_CAPACITY); //利用扩容函数初始化基础数组
size = 0;
//因为进队操作rear要加一,所以rear要初始化为-1,
//当第一个元素进队,rear刚好指向第一个元素的下标0,以后每次进队加一
rear = -1;
//因为第一次进队元素被放在下标为0的位置,所以front默认初始化为0,
//刚好是第一个元素下标
front = 0;
}
//扩容操作
private void ensureCapacity(int newCapacity) {
if(newCapacity <= size) //必须要保证新容量要比当前元素个数要大,
return; //否则扩容没有什么意义
AnyType[] oldDataElem = dataElem;
//不能创建泛型数组,所以先创建Object类型数组,然后强转
dataElem = (AnyType[]) new Object[newCapacity];
for(int i = 0; i < size(); i++)
dataElem[i] = oldDataElem[i];
}
//返回当前队列储存元素的个数
public int size() {
return size;
}
//入队,每次入队要将rear、size加一
public void enqueue(AnyType elem){
if(size() == dataElem.length) //容量不够,扩容
ensureCapacity(size() * 2);
//每当rear要越过dataElem.length - 1时,
//就要让rear归为0(因为此时数组首端可能还有空位,前面经过了出队操作,
//导致了front前移,而且size还没有等于0),
//否则会出现ArrayIndexOutOfBoundsException
if(++rear == dataElem.length)
rear = 0;
dataElem[rear] = elem;
size++;
}
//出队,每次出队要将front加一,size减一
public AnyType dequeue(){
if(size() == 0)
throw new NoSuchElementException();
AnyType oldValue = dataElem[front++];
//因为这是一个循环队列,每当front要越过dataElem.length - 1时,
//就要让front归为0(因为此时数组首端还有元素,size还没有等于0),
//否则会出现ArrayIndexOutOfBoundsException
if(front == dataElem.length)
front = 0;
size--;
return oldValue;
}
//得到队列顶端元素,即front指向的元素
public AnyType get() {
return dataElem[front];
}
}
这版代码咋一看没什么问题,但实际上扩容操作即ensureCapacity方法有着极大问题,因为如果队列经过出队操作,那么满的时候front必在rear前面,即比rear更大,且大1。此时,如果对扩容之后的数组进行进队操作,将会覆盖掉前面已经存在的队列元素。问题如下图所示,
经过我一个小时的思考之后,我终于找到了解决办法
第二版代码(基本一样,就扩容操作发生了改变)
public class ArrayQueue<AnyType> {
private AnyType[] dataElem;
private int size;
private static final int DEFAULT_CAPACITY = 10;
private int front;
private int rear;
public ArrayQueue(){
dataElem = (AnyType[]) new Object[DEFAULT_CAPACITY];
size = 0;
rear = -1;
front = 0;
}
//扩容操作
private void ensureCapacity(int newCapacity) {
if(newCapacity <= size)
return;
AnyType[] oldDataElem = dataElem;
dataElem = (AnyType[]) new Object[newCapacity];
//新数组依然从0开始赋值
//但旧数组从对头开始赋值,即front位置,
//这样就保证了新数组的第一个元素必然是队头,最后一个元素必然是队尾,
//方便了以后进队操作,而且时间复杂度基本没有变化
for(int i = 0; i < size(); i++){
dataElem[i] = oldDataElem[front++];
if(front == oldDataElem.length)
front = 0;
}
front = 0;
rear = size() - 1;
}
public int size() {
return size;
}
//入队
public void enqueue(AnyType elem){
if(size() == dataElem.length)
ensureCapacity(size() * 2);
if(++rear == dataElem.length)
rear = 0;
dataElem[rear] = elem;
size++;
}
//出队
public AnyType dequeue(){
if(size() == 0)
throw new NoSuchElementException();
AnyType oldValue = dataElem[front++];
if(front == dataElem.length)
front = 0;
size--;
return oldValue;
}
//得到队列顶端元素,即front指向的元素
public AnyType get() {
return dataElem[front];
}
}
好了,以上就是我的见解,如果有错,欢迎指正。