队列 Queue
队列也是一种线性结构
相比数组,队列对应的操作是数组的子集
只能从一端(队尾)添加元素,只能从另一端(队首)取出元素
队列是一种先进先出 的数据结构(先到先得 First In First Out FIFO)
其实队列⾮常好理解,我们将队列可以看成⼩朋友排队
队尾的⼩朋友到指定的地点了–>出队
有新的⼩朋友加⼊了–>⼊队
相对于栈⽽⾔,队列的特性是:先进先出
队列的基本实现
Interface Queue<E>
— ArrayQueue<E>
void enqueue(E)
E dequeue()
E getFront()
int getSize()
boolean isEmpty()
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 package demo.structure.queue;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 package demo.structure.queue;import demo.structure.array.Array;public class ArrayQueue <E > implements Queue <E > { Array<E> array; public ArrayQueue (int capacity) { array = new Array<>(capacity); } public ArrayQueue () { array = new Array<>(); } @Override public int getSize () { return array.getSize(); } @Override public boolean isEmpty () { return array.isEmpty(); } public int getCapacity () { return array.getCapacity(); } @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 result = new StringBuilder(); result.append("Queue:" ); result.append("front [" ); for (int i = 0 ; i < array.getSize(); i++) { result.append(array.get(i)); if (i != array.getSize() - 1 ) { result.append("," ); } } result.append("] tail" ); return result.toString(); } }
测试代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package demo.structure.queue;public class ArrayQueueMain { public static void main (String[] args) { ArrayQueue<Integer> 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); } } } }
执行结果
线性队列的复杂度分析
ArrayQueue<E>
void enqueue(E) – O(1) 均摊
E dequeue() – O(n)
E getFront() – O(1)
int getSize() – O(1)
boolean isEmpty() – O(1)
数组实现循环队列
做成循环队列的好处 是不浪费内存资源!
front == tail 队列为空
(tail+1) % c == front 队列满
capacity中,浪费一个空间
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 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 package demo.structure.queue;public class LoopQueue <E > implements Queue <E > { private E[] data; private int front, tail; private int size; 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 == tail; } @Override public void enqueue (E e) { if ((tail + 1 ) % data.length == front) { resize(getCapacity() * 2 ); } data[tail] = e; tail = (tail + 1 ) % data.length; size++; } @Override public E dequeue () { if (isEmpty()) { throw new IllegalArgumentException("Cannot dequeue from an empty queue" ); } 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("Queue is empty" ); } return data[front]; } private void resize (int newCapacity) { E[] newData = (E[]) new Object[newCapacity + 1 ]; for (int i = 0 ; i < size; i++) { newData[i] = data[(i + front) % data.length]; } data = newData; front = 0 ; tail = size; } @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(); } }
测试代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package demo.structure.queue;public class LoopQueueMain { public static void main (String[] args) { LoopQueue<Integer> queue = new LoopQueue<>(); for (int i = 0 ; i < 10 ; i++) { queue.enqueue(i); System.out.println(queue); if (i % 3 == 2 ) { queue.dequeue(); System.out.println(queue); } } } }
执行结果
循环队列的复杂度分析
LoopQueue<E>
void enqueue(E) – O(1) 均摊
E dequeue() – O(1) 均摊
E getFront() – O(1)
int getSize() – O(1)
boolean isEmpty() – O(1)
数组线性队列和数组循环队列的比较 测试代码:
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 package demo.structure.queue;import java.util.Random;public class Main { public static void main (String[] args) { int opCount = 100000 ; ArrayQueue<Integer> arrayQueue = new ArrayQueue<>(); double time1 = testQueue(arrayQueue, opCount); System.out.println("ArrayQueue, time:" + time1 + " s" ); LoopQueue<Integer> loopQueue = new LoopQueue<>(); double time2 = testQueue(loopQueue, opCount); System.out.println("LoopQueue, time:" + time2 + " s" ); } private static double testQueue (Queue<Integer> q, int opCount) { long startTime = System.nanoTime(); Random random = new Random(); for (int i = 0 ; i < opCount; i++) { q.enqueue(random.nextInt(Integer.MAX_VALUE)); } for (int i = 0 ; i < opCount; i++) { q.dequeue(); } long endTime = System.nanoTime(); return (endTime - startTime) / 1000000000.0 ; } }
执行结果:
换个方式实现队列
使用size,不浪费一个空间
不使用size,浪费一个空间
不浪费一个空间实现队列 实际上,如果我们想要实现一个队列,使用size记录了队列中有多少元素,完全可以不浪费那一个空间。
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 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 package demo.structure.queue;public class LoopQueueSize <E > implements Queue <E > { private E[] data; private int front, tail; private int size; public LoopQueueSize (int capacity) { data = (E[]) new Object[capacity]; front = 0 ; tail = 0 ; size = 0 ; } public LoopQueueSize () { this (10 ); } @Override public int getSize () { return size; } public int getCapacity () { return data.length; } @Override public boolean isEmpty () { return size == 0 ; } @Override public void enqueue (E e) { if (size == getCapacity()) { resize(getCapacity() * 2 ); } data[tail] = e; tail = (tail + 1 ) % data.length; size++; } @Override public E dequeue () { if (isEmpty()) { throw new IllegalArgumentException("Cannot dequeue from an empty queue" ); } 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("Queue is empty" ); } return data[front]; } private void resize (int newCapacity) { E[] newData = (E[]) new Object[newCapacity]; for (int i = 0 ; i < size; i++) { newData[i] = data[(i + front) % data.length]; } data = newData; front = 0 ; tail = size; } @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 = 0 ; i < size; i++) { res.append(data[(front + 1 ) % data.length]); if ((i + front + 1 ) % data.length != tail) { res.append("," ); } } res.append("] tail" ); return res.toString(); } public static void main (String[] args) { LoopQueueSize<Integer> queue = new LoopQueueSize<>(); for (int i = 0 ; i < 10 ; i++) { queue.enqueue(i); System.out.println(queue); if (i % 3 == 2 ) { queue.dequeue(); System.out.println(queue); } } } }
使用size实现队列,会浪费一个空间 如果我们不使用size,也完全可以实现整个队列,但是,相应的,我们需要浪费一个空间
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 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 package demo.structure.queue;public class LoopQueueNoSize <E > implements Queue <E > { private E[] data; private int front, tail; public LoopQueueNoSize (int capacity) { data = (E[]) new Object[capacity]; front = 0 ; tail = 0 ; } public LoopQueueNoSize () { this (10 ); } @Override public int getSize () { return tail >= front ? tail - front : tail - front + data.length; } public int getCapacity () { return data.length - 1 ; } @Override public boolean isEmpty () { return front == tail; } @Override public void enqueue (E e) { if ((tail + 1 ) % data.length == front) { resize(getCapacity() * 2 ); } data[tail] = e; tail = (tail + 1 ) % data.length; } @Override public E dequeue () { if (isEmpty()) { throw new IllegalArgumentException("Cannot dequeue from an empty queue" ); } E ret = data[front]; data[front] = null ; front = (front + 1 ) % data.length; if (getSize() == getCapacity() / 4 && getCapacity() / 2 != 0 ) { resize(getCapacity() / 2 ); } return ret; } @Override public E getFront () { if (isEmpty()) { throw new IllegalArgumentException("Queue is empty" ); } return data[front]; } private void resize (int newCapacity) { E[] newData = (E[]) new Object[newCapacity + 1 ]; int sz = getSize(); for (int i = 0 ; i < sz; i++) { newData[i] = data[(i + front) % data.length]; } data = newData; front = 0 ; tail = sz; } @Override public String toString () { StringBuilder res = new StringBuilder(); res.append(String.format("Queue:size = %d, capacity = %d \n" , getSize(), 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) { LoopQueueNoSize<Integer> queue = new LoopQueueNoSize<>(); for (int i = 0 ; i < 10 ; i++) { queue.enqueue(i); System.out.println(queue); if (i % 3 == 2 ) { queue.dequeue(); System.out.println(queue); } } } }
双端队列 Deque
可以在队列的两端添加元素,addFront、addLast
可以在队列的两端删除元素,removeFront,removeLast
双端队列的实现 对于双端队列,其实removeFront和队列中dequeue是一样的;addLast和队列enqueue是一样的。关键是实现addFront和removeLast两个方法,做出添加和删除操作以后,front和last。
使用size来表示双端队列中的元素个数,于此同时,我们的双端队列不浪费空间。
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 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 package demo.structure.queue;public class Deque <E > { private E[] data; private int front, tail; private int size; public Deque (int capacity) { data = (E[]) new Object[capacity]; front = 0 ; tail = 0 ; size = 0 ; } public Deque () { this (10 ); } public int getCapacity () { return data.length; } public boolean isEmpty () { return size == 0 ; } public int getSize () { return size; } public void addLast (E e) { if (size == getCapacity()) { resize(getCapacity() * 2 ); } data[tail] = e; tail = (tail + 1 ) % data.length; size++; } public void addFront (E e) { if (size == getCapacity()) { resize(getCapacity() * 2 ); } front = front == 0 ? data.length - 1 : front - 1 ; data[front] = e; size++; } public E removeFront () { if (isEmpty()) { throw new IllegalArgumentException("Cannot dequeue from an empty queue" ); } E ret = data[front]; data[front] = null ; front = (front + 1 ) % data.length; size--; if (getSize() == getCapacity() / 4 && getCapacity() / 2 != 0 ) { resize(getCapacity() / 2 ); } return ret; } public E removeLast () { if (isEmpty()) { throw new IllegalArgumentException("Cannot dequeue from an empty queue" ); } tail = tail == 0 ? data.length - 1 : tail - 1 ; E ret = data[tail]; data[tail] = null ; size--; if (getSize() == getCapacity() / 4 && getCapacity() / 2 != 0 ) { resize(getCapacity() / 2 ); } return ret; } public E getFront () { if (isEmpty()) { throw new IllegalArgumentException("Cannot dequeue from an empty queue" ); } return data[front]; } public E getLast () { if (isEmpty()) { throw new IllegalArgumentException("Cannot dequeue from an empty queue" ); } int index = tail == 0 ? data.length - 1 : tail - 1 ; return data[index]; } private void resize (int newCapacity) { E[] newData = (E[]) new Object[newCapacity]; for (int i = 0 ; i < size; i++) { newData[i] = data[(i + front) % data.length]; } data = newData; front = 0 ; tail = size; } @Override public String toString () { StringBuilder builder = new StringBuilder(); builder.append(String.format("Queue:size=%d,capacity=%d" , getSize(), getCapacity())); builder.append("front [" ); for (int i = 0 ; i < size; i++) { builder.append(data[(i + front) % data.length]); if (i != size - 1 ) { builder.append("," ); } } builder.append("] tail" ); return builder.toString(); } public static void main (String[] args) { Deque<Integer> dq = new Deque<>(); for (int i = 0 ; i < 16 ; i++) { if (i % 2 == 0 ) { dq.addLast(i); } else { dq.addFront(i); } System.out.println(dq); } System.out.println(); for (int i = 0 ; !dq.isEmpty(); i++) { if (i % 2 == 0 ) { dq.removeFront(); } else { dq.removeLast(); } System.out.println(dq); } } }