有界循环队列
使用循环数组实现有界循环队列:
参考: Implementing a Queue using a circular array
1/**
2 * 有界循环队列,实际上是循环数组实现。
3 * 参考:<a href=
4 * "http://www.mathcs.emory.edu/~cheung/Courses/171/Syllabus/8-List/array-queue2.html">
5 * Implementing a Queue using a circular array</a>
6 * <p>
7 * 另一种实现思路:<a href="https://www.baeldung.com/java-ring-buffer">Implementing a
8 * Ring Buffer in Java</a>
9 * <p>
10 * 上述两种思路的主要差别是:
11 * <ul>
12 * <li>
13 * read(入队)指针的指向不同。前者指向的是空槽,而后者指向的是队尾的元素;
14 * </li>
15 * <li>
16 * 指针的内涵不同,前者对应的是数组的index,而后者对应的是入队/出队的次数;
17 * </li>
18 * </ul>
19 * <p>
20 * 这种设计的差别,最终导致队列满/空判断上面的差异。
21 * <p>
22 *
23 * <pre>
24 * 循环队列的数据简单模型图——有8个slot的定容数组:此时count = 4
25 * (o代表此槽为空,N为数组容量)
26 * =========================================================
27 * [0] [1] [2] [3] [4] [5] [6] [7]
28 * | o | o | 12 | 34 | 65 | 26 | o | o |
29 * read write
30 * =========================================================
31 * </pre>
32 * <p>
33 * 在之前的顺序队列中,如果<code>write = N && read!=0</code>的情况下,
34 * 数组的空间可以重复利用,当时的处理办法是对数组中的元素进行"整理";
35 * <p>
36 * 在循环队列中,我们无需再这样做,队列中的slot可以重复使用,
37 * slot的值可以一直被覆盖写入,而队列真正
38 * 需要关心的内容是<b>read</b>和<b>write</b>指针:<br>
39 *
40 * <pre>
41 * 当循环队列入队时,
42 * =========================================================
43 * [0] [1] [2] [3] [4] [5] [6] [7]
44 * | o | o | 12 | 34 | 65 | 26 | 19 | o |
45 * read write
46 * =========================================================
47 * =========================================================
48 * [0] [1] [2] [3] [4] [5] [6] [7]
49 * | o | o | 12 | 34 | 65 | 26 | 19 | 32 |
50 * write read
51 * =========================================================*
52 * write指针后移一位,即<code>write +=write</code>。
53 * 不过,当write指针指向数组的末尾时,write该如何计算呢?
54 * 事实上,由于循环数组的slot一直在重用,故write的取值总介于[0,N)之间,
55 * 因此当有元素入队时,可用
56 * <code>write = (write +1) % N</code>计算得到write指针指向。
57 *
58 * 同理,当循环队列有元素出队时,
59 * =========================================================
60 * [0] [1] [2] [3] [4] [5] [6] [7]
61 * | o | o | 12 | 34 | 65 | 26 | 19 | o |
62 * read write
63 * =========================================================
64 * 和入队类似,<code>read = (read + 1) % N</code>
65 * </pre>
66 * </p>
67 * 不难发现,如果read指针追上write指针,即read==write时,队列为空;
68 * 反之,若write指针追上read指针,即read==write时,
69 * 队列为满。<b>如何消除歧义?</b>
70 *
71 * <p>
72 * 有2种办法消除歧义:
73 * <ul>
74 * <li>1. 引入整型变量{@link CircularBuffer#count},用于实时统计队列中的元素个数</li>
75 * <li>
76 * 2. 取个巧,将write指针对应的slot闲置,如果发现write指针的下一个指针是read指针的时候,
77 * 即认为队列满了。这种情况下,队列实际的容量为N-1。
78 * </li>
79 * </ul>
80 * <p>
81 * 如果按照方法2,队列为空的判断条件不变,仍为<code>read == write</code>;
82 * 但是队列为满的条件变了,即若
83 * <code>
84 * read == (write + 1) % N
85 * </code>
86 * 时,认为队列已满。
87 * <p>
88 * 循环数组的效率很高,空间复杂度为O(1),入队和出队的时间复杂度均为O(1)。
89 *
90 * @author wangy
91 * @version 1.0
92 * @date 2021/7/1 / 10:52
93 * @see ArrayQueue
94 * @see CircularBufferVariety
95 */
96public class CircularBuffer {
97 private int[] data;
98
99 private int read;
100
101 private int write;
102 // 队列中元素计数
103 private int count;
104
105 public CircularBuffer(int cap) {
106 this.data = new int[cap];
107 this.read = this.write = this.count = 0;
108 }
109
110 boolean isFull() {
111 return (write + 1) % data.length == read;
112 }
113
114 boolean isEmpty() {
115 return read == write;
116 }
117
118 int enQueue(int e) {
119 if (isFull())
120 throw new RuntimeException("Queue full!");
121 data[write] = e;
122 write = (write + 1) % data.length;
123 return e;
124 }
125
126 int deQueue() {
127 if (isEmpty())
128 throw new RuntimeException("Queue empty!");
129 int e = data[read];
130 read = (read + 1) % data.length;
131 return e;
132 }
133
134 void printAll() {
135 System.out.println("数组:" + Arrays.toString(data));
136 System.out.print("队列:[");
137 // 从队头到队尾
138 for (int i = read; i % data.length != write; i++) {
139 System.out.print(data[i % data.length] + " ");
140 }
141 System.out.println("]");
142 }
143
144 public static void main(String[] args) {
145 // 实际上,仅可以入队3个元素
146 CircularBuffer cb = new CircularBuffer(4);
147 cb.enQueue(1);
148 cb.deQueue();
149 cb.enQueue(2);
150 cb.enQueue(3);
151 cb.enQueue(5);
152 cb.deQueue();
153 cb.enQueue(7);
154 cb.printAll();
155 // full exception
156 cb.enQueue(9);
157 }
158}