队列和栈其实很类似,最大的不同就是栈是先将后出,而队列是先进先出,就像一根水管固定一端进,而另一端负责出
队列又分为数组队列和循环队列,数组队列dequeue(出队)方法的时间复杂度为O(n),而循环队列很好的优化了这点,下面通过代码来看看它们是如何实现的.
数组队列(ArrayQueue)
接口queue:
public interface Queue {
//获取大小
int getSize();
//是否为空
boolean isEmpty();
//添加数据
void enqueue(E e);
//取出数据
E dequeue();
//获取第一个数据
E getFront();
}
实现:ArrayQueue
public class ArrayQueue implements Queue {
private Array array;
public ArrayQueue(int capacity){
array = new Array(capacity);
}
public ArrayQueue(){
this(10);
}
@Override
public int getSize() {
return array.getSize();
}
@Override
public boolean isEmpty() {
return array.isEmpty();
}
@Override
public void enqueue(E e) {
array.addLast(e);
}
@Override
public E dequeue() {
return array.removeFirst();
}
@Override
public E getFront() {
return array.getFirst();
}
@Override
public String toString() {
StringBuilder res = new StringBuilder();
res.append("Queue: ");
res.append("front [");
for(int i = 0 ; i < array.getSize() ; i ++){
res.append(array.get(i));
if(i != array.getSize() - 1)
res.append(", ");
}
res.append("]");
return res.toString();
}
}
测试:
循环10次入队,每过3次出队
public static void main(String[] args) {
ArrayQueue queue = new ArrayQueue<>();
for(int i = 0 ; i < 10 ; i ++){
queue.enqueue(i);
System.out.println(queue);
if(i % 3 == 2){
queue.dequeue();
System.out.println(queue);
}
}
}
结果打印
Queue: front [0]
Queue: front [0, 1]
Queue: front [0, 1, 2]
Queue: front [1, 2]
Queue: front [1, 2, 3]
Queue: front [1, 2, 3, 4]
Queue: front [1, 2, 3, 4, 5]
Queue: front [2, 3, 4, 5]
Queue: front [2, 3, 4, 5, 6]
Queue: front [2, 3, 4, 5, 6, 7]
Queue: front [2, 3, 4, 5, 6, 7, 8]
Queue: front [3, 4, 5, 6, 7, 8]
Queue: front [3, 4, 5, 6, 7, 8, 9]
Process finished with exit code 0
循环队列(LoopQueue)
如图所示,此时队列虽然有两次出队操作是坐标
0和1
此时为空,但按照数组
队列来说,新入队的h仍会判断此时队列已满--造成扩容
,是原本时间复杂度为O(1)的入队操作变回O(n)
,而循环队列
将我们最后一位(tail
)直接指向了坐标0
,这样就既可以利用前面出队后空出来的0地址又减少了扩容操作
,这就是循环队列.
实现LoopQueue:
基础部分:
public class LoopQueue implements Queue {
private int size;
private int tail;
private int front;
private E[] data;
public LoopQueue(int capacity){
data = (E[]) new Object[capacity+1];
front = 0;
tail = 0;
size = 0;
}
public LoopQueue(){
this(10);
}
public int getCapacity(){
return data.length - 1;
}
@Override
public int getSize() {
return size;
}
@Override
public boolean isEmpty() {
return front==size;
}
@Override
public E getFront() {
return data[front];
}
}
这里主要需要注意的由于tail是指向队尾的下一个位置,所以在初始化时数组的size+1
,相应的返回的队列长度时也需要-1
动态扩容:
private void resize(int capacity) {
E[] newData = (E[]) new Object[capacity];
for (int i = 0; i < size; i++) {
newData[i] = data[((i+front)%data.length)];
}
data = newData;
tail = size;
front = 0;
}
这里注意在新的队列中需要将旧的数据从头开始排列(data[((i+front)%data.length)])
入队和出队:
@Override
public void enqueue(E e) {
if ((tail+1)%data.length==front) {
resize(getCapacity()*2);
}
data[tail] = e;
tail = (tail+1)%data.length;
size++;
}
private void resize(int capacity) {
E[] newData = (E[]) new Object[capacity];
for (int i = 0; i < size; i++) {
newData[i] = data[((i+front)%data.length)];
}
data = newData;
tail = size;
front = 0;
}
@Override
public E dequeue() {
if(isEmpty())
throw new IllegalArgumentException("Cannot dequeue from an empty queue.");
E e = data[front];
data[front] = null;
front = (front+1)%data.length;
size--;
if(size == getCapacity() / 4 && getCapacity() / 2 != 0)
resize(getCapacity() / 2);
return e;
}
测试:
@Override
public String toString(){
StringBuilder res = new StringBuilder();
res.append(String.format("Queue: size = %d , capacity = %d\n", size, getCapacity()));
res.append("front [");
for(int i = front ; i != tail ; i = (i + 1) % data.length){
res.append(data[i]);
if((i + 1) % data.length != tail)
res.append(", ");
}
res.append("] tail");
return res.toString();
}
public static void main(String[] args){
LoopQueue queue = new LoopQueue<>(5);
for(int i = 0 ; i < 10 ; i ++){
queue.enqueue(i);
System.out.println(queue);
if(i % 3 == 2){
queue.dequeue();
System.out.println(queue);
}
}
}
输出:
Queue: size = 1 , capacity = 5
front [0] tail
Queue: size = 2 , capacity = 5
front [0, 1] tail
Queue: size = 3 , capacity = 5
front [0, 1, 2] tail
Queue: size = 2 , capacity = 5
front [1, 2] tail
Queue: size = 3 , capacity = 5
front [1, 2, 3] tail
Queue: size = 4 , capacity = 5
front [1, 2, 3, 4] tail
Queue: size = 5 , capacity = 5
front [1, 2, 3, 4, 5] tail
Queue: size = 4 , capacity = 5
front [2, 3, 4, 5] tail
Queue: size = 5 , capacity = 5
front [2, 3, 4, 5, 6] tail
Queue: size = 6 , capacity = 10
front [2, 3, 4, 5, 6, 7] tail
Queue: size = 7 , capacity = 10
front [2, 3, 4, 5, 6, 7, 8] tail
Queue: size = 6 , capacity = 10
front [3, 4, 5, 6, 7, 8] tail
Queue: size = 7 , capacity = 10
front [3, 4, 5, 6, 7, 8, 9] tail
Process finished with exit code 0
动态数组和循环数组效率比较
public class Main {
//测试使用q出队入队opCount次,所需要的时间,单位:秒
private static Double testQueue(Queue queue, int opCount){
long startTime = System.currentTimeMillis();
Random random = new Random();
for (int i = 0; i < opCount; i++) {
queue.enqueue(random.nextInt(Integer.MAX_VALUE));
}
for (int i = 0; i < opCount; i++) {
queue.dequeue();
}
long finishTime = System.currentTimeMillis();
return (finishTime-startTime)/1000.0;
}
public static void main(String[] args) {
int opCount = 100000;
ArrayQueue arrayQueue = new ArrayQueue();
System.out.println("arrayQueue, time = " +testQueue(arrayQueue,opCount));
LoopQueue loopQueue = new LoopQueue();
System.out.println("loopQueue, time = " +testQueue(loopQueue,opCount));
}
}
结果:
arrayQueue, time = 6.641
loopQueue, time = 0.01
Process finished with exit code 0
可以看出循环队列的效率大大强于普通的数组队列