目录
前言
循环队列
,是为了解决 数组队列
,deQueue()
复杂度为 O(n)
的问题;
底层,我们不再使用之前我们实现的 动态数组
,因为它的 reSize()
不符合我们的要求,对 循环队列
的扩容,要另写一套方法 ;
思想
循环队列,deQueue()
操作的时候,是不会将后面的元素,往前移动的,它使用一个标记 front
来记录当前队首在哪里,同样,由于后面的元素不会往前移动,那么数组的前端就会有空缺,当数组后面装满了的时候,就会再前面继续装 ;这里为了表示队尾在哪,用一个 tail
标记 ;
只有当数组的前后,都装满了,才会进行数组的扩容 ;
为了便于逻辑的编写,我们有意识的丢弃一个空间不用,这样当 front == tail
就表示队列为空,当 (tail + 1 ) % (数组长度-1) == front
就表示队列满了 ;
已实现方法
注意点
在进行 reSize()
操作的时候,我们要保证数组的最少长度为 3
;
因为,在我们的底层 toString()
方法中,有一个 bug
:当满足数组长度为 2
,队列中只有一个元素,然后执行出队列操作,就会触发这个 bug
;
toString()
代码(bug在for循环中)
@Override
public String toString() {
StringBuilder res = new StringBuilder();
res.append(String.format("LoopQueue : size = %d , capacity = %d ,front = %d ,tail = %d ,length = %d;
", getSize(), getCapacity(), front, tail, array.length));
res.append("Front [ ");
for (int i = front; i != tail; i = (i + 1) % (array.length - 1)) {
res.append(array[i]);
if (!((i + 1) % (array.length - 1) == tail)) {
res.append(", ");
}
}
res.append(" ] Tail ");
return res.toString();
}
产生这个问题的原因很简单
呜呜呜,我debug了一上午,换来一个原因很简单。。
当满足上面触发 bug
条件的时候, front = 0 、tail = 1 array.length = 2
或者 front = 1 、tail = 0 array.length = 2
;带入 for
循环,爽了,都是个死循环 ;
解决方法有2种:
使用下面的方法遍历队列;或者在 reSize()
的时候,保证数组长度最小为 3
;
for (int i = 0; i < size; i++) {
res.append(array[(front + i) % (array.length - 1)]);
if (i != size - 1) {
res.append(", ");
}
}
关键点在于不能让 array.length - 1 == 1
;不然循环队列,就无法循环了;
因此,稳妥点,还是保证数组的长度,比较靠谱;
private void reSize(int newCapacity) {
// 保证了数组的长度,最少是 3
E[] array = (E[]) new Object[newCapacity + 2];
// 复制原数组到新数组
for (int i = 0; i < size; i++) {
array[i] = this.array[(front + i) % (this.array.length - 1)];
}
this.array = array;
front = 0;
tail = size;
}
java 代码
----------- Queue 接口 --------------
package xin.ijava.quene;
/**
* 数组队列 、 循环队列
* 时间比较 ;
* @author An
*/
@SuppressWarnings("unused")
public interface Queue<E> {
/**
* 获取队列中的实际元素个数
* @return 元素个数
*/
int getSize() ;
/**
* 队列是否为空
* @return 队列的情况
*/
boolean isEmpty() ;
/**
* 获取当其队列的队首元素
* @return 队首元素
*/
E getFront();
/**
* 移除当前队首元素
* @return 队首元素
*/
E deQueue() ;
/**
* 将元素添加到 队尾
* @param e 目标元素
*/
void enQueue(E e) ;
}
------------- 循环队列实现类 ---------------
package xin.ijava.quene;
/**
* 循环队列,解决 enQueue() 复杂度为 O(n) 的问题 ;
* <p>
* 底层实现不再是使用我们的动态数组;
* 只是使用普通的数组,但是我们将要赋予它动态伸缩的能力 ;
*
* @author An
*/
public class LoopQueue<E> implements Queue<E> {
/**
* 底层维护的可怜弱小又无助的普通数组 ;
*/
private E[] array;
/**
* 其实存储了多少个元素,可以根据 front 和 tail 算出来的 ;
* 但是为了方便逻辑的编写,我们还是用一个变量记录下,等会挑战下,不记录的版本 ;
*/
private int size;
private int front, tail;
public LoopQueue() {
array = (E[]) new Object[10];
size = 0;
front = tail = 0;
}
/**
* 真实开启内存的时候,比用户传进来的参数大一个 ,因为,我们要故意的抛弃一个内存不用
*
* @param capacity
*/
public LoopQueue(int capacity) {
array = (E[]) new Object[capacity + 1];
size = 0;
front = tail = 0;
}
@Override
public int getSize() {
return size;
}
@Override
public boolean isEmpty() {
return size == 0;
}
@Override
public E getFront() {
return array[front];
}
@Override
public E deQueue() {
if (isEmpty()) {
throw new IllegalArgumentException("Queue is already empty ,can not execute deQueue method;");
}
/**
* 移除队首元素,我们不再进行元素的移动,只是移动记录队首的标记
* 移动的时候,注意,对数组的容量进行取余,防止越界 !
*/
/**
* 在进行缩容的时候,要保证数组大小大于等于3,因为在我们的toString 中,使用的遍历数组的方法有bug
*
* array.length / 2 最小值是 1 ;
*/
if (size == array.length / 4 && array.length / 2 != 0) {
reSize(array.length / 2);
}
E temp = array[front];
front = (front + 1) % (array.length - 1);
size--;
return temp;
}
@Override
public void enQueue(E e) {
// 判断是否需要扩容
if ((tail + 1) % (array.length - 1) == front) {
reSize(2 * (array.length - 1));
}
array[tail] = e;
tail = (tail + 1) % (array.length - 1);
size++;
}
private void reSize(int newCapacity) {
// 保证了数组的长度,最少是 3
E[] array = (E[]) new Object[newCapacity + 2];
// 复制原数组到新数组
for (int i = 0; i < size; i++) {
array[i] = this.array[(front + i) % (this.array.length - 1)];
}
this.array = array;
front = 0;
tail = size;
}
public int getCapacity() {
return array.length - 1;
}
@Override
public String toString() {
StringBuilder res = new StringBuilder();
res.append(String.format("LoopQueue : size = %d , capacity = %d ,front = %d ,tail = %d ,length = %d;
", getSize(), getCapacity(), front, tail, array.length));
res.append("Front [ ");
// 第二种方法 遍历队列,方法有 bug 的,
// 这样做,当数组长度为2的时候,第一个位置上有元素,然后执行出队列操作
// 然后打印队列的时候,下面的循环是死循环的,解决
// 解决方法有2种:使用下面的方法遍历队列;或者在reSize的时候,保证数组长度最小为3
for (int i = front; i != tail; i = (i + 1) % (array.length - 1)) {
res.append(array[i]);
if (!((i + 1) % (array.length - 1) == tail)) {
res.append(", ");
}
}
// for (int i = 0; i < size; i++) {
// res.append(array[(front + i) % (array.length - 1)]);
// if (i != size - 1) {
// res.append(", ");
// }
// }
res.append(" ] Tail ");
return res.toString();
}
}