引言:数据结构——循环队列
LoopQueue
在实现完ArrayQueue
之后,发现ArrayQueue
有一个致命的缺点,就是出队列需要O(n)的时间复杂度,因为每次出队时,后面的元素都要向前移动一个空间,所以造成了时间复杂度上升
怎么解决这个问题呢?
在出队之后,我们可以不移动,使用一个front
和tail
来标记队头和队尾,再把队头队尾连起来,实现一个循环队列
这样当出队时,只需要front++
,入队只需要tail++
,时间复杂度都是O(1),解决了上述的问题
循环队列需要什么方法

这里我们会继续实现Queue
这个接口,Queue
接口的代码如下,和
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
public interface Queue<E> { int getSize(); boolean isEmpty();
void enqueue(E e);
E dequeue();
E getFront(); }
|
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
| package queue;
import java.util.StringJoiner;
public class LoopQueue<E> implements Queue { private E[] data; private int front , tail; private int size; private final static int initialCapacity = 10;
public LoopQueue(int capacity) { data = (E[]) new Object[capacity+1];
front = 0; tail = 0; size = 0; }
public LoopQueue() { this(initialCapacity); }
public int getCapacity(){ return data.length - 1; }
@Override public int getSize() { return size; }
@Override public boolean isEmpty() { return front == tail; }
@Override public void enqueue(Object o) { if((tail +1 )% data.length == front){ resize(getCapacity() * 2); } data[tail] = (E) o; tail = (tail +1)% data.length; size++; }
private void resize(int newCapacity) { E[] newData = (E[]) new Object[newCapacity];
for (int i = 0; i < size; i++) { newData[i] = data[(front + i) % data.length]; } data = newData; front = 0; tail = size; }
@Override public E dequeue() { if(size == 0){ throw new IllegalArgumentException("队列为空,不能删除"); } E ret = data[front]; data[front] = null; front = (front+1)%data.length; size--;
if(size == getCapacity()/4 && getCapacity()/2!=0){ resize(getCapacity()/2); } return ret; }
@Override public E getFront() { if (isEmpty()){ throw new IllegalArgumentException("队列为空"); } return data[front]; }
@Override public String toString() { StringJoiner sj = new StringJoiner(",","LoopQueue: front [","] tail ");
for(int i =front; i!=tail ;i= (i+1)%data.length){ sj.add(data[i]+""); } return sj.toString(); } }
|
复杂度分析
1 2 3 4 5 6 7
| 方法名 | 时间复杂度 | ------------------------------------------------- enqueue(E e) | O(1) | (均摊复杂度) dequeue() | O(1) | (均摊复杂度) front() | O(1) | getSize() | O(1) | isEmpty() | O(1) |
|
ArrayQueue 对比 LoopQueue
数组队列和循环队列,他们之间的差距只在dequeue()
这个方法上,一个是O(n)
,一个是O(1)
这样的差距是天翻地覆的,下面的测试代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public class Main { public static void main(String[] args) { int loopTurn = 500_000; Queue<Integer> arrayQueue = new ArrayQueue<>(); Queue<Integer> loopQueue = new LoopQueue<>(); System.out.println("arrayQueue运行时间:"+testQueue(loopTurn,arrayQueue)+"s"); System.out.println("loopQueue运行时间:"+testQueue(loopTurn,loopQueue)+"s"); }
private static double testQueue(int loopTurn, Queue<Integer> q) { long startTime = System.nanoTime(); for(int i=0;i<loopTurn;i++) { Random r = new Random(); q.enqueue(r.nextInt(Integer.MAX_VALUE)); } for(int i=0;i<loopTurn;i++) { q.dequeue(); } long endTime = System.nanoTime(); return (endTime - startTime) / 1000000000.0; } }
|
让他们各自分别执行五十万次,(原本想测试一百万次的,但是觉得太慢了)
在我的电脑上得到如下成绩
1 2
| arrayQueue运行时间:70.0025624s loopQueue运行时间:0.0590492s
|
嘶!倒吸一口凉气,要不是我的音乐还在播放,我会以为我电脑卡了的。
参考资料
参考资料:
来自慕课liuyubobobo的教程