Queue II: circular index calculation, deque, priority queue
The empty/full formulas of a circular queue without count, the double-ended queue (deque), the logic of the priority queue, and simulation with queues. The exam asks for finding indices with formulas and simulation traces.
Last week we wrote the two basic implementations of a queue. This week there are three extensions. First we work on the count-free version of the circular array and its index formulas, because the exam has pure formula questions like “front = 4, rear = 1, how many elements are there?”. Then we see the deque, which can operate at both ends, followed by the priority queue, which removes elements in priority order. Finally we follow simulations, the real use of queues.
Golden rules:
- In a circular index everything is
% cap. When going backward, add+ capfirst. - If there is no count, one cell stays empty. Empty:
front == rear. Full:(rear + 1) % cap == front. - A deque is a double-ended queue. It can be used both like a stack and like a queue.
- A priority queue is not FIFO. In Java the smallest comes out first.
The circular queue: without count
Last week we saw that the case front == rear can mean both empty and full, and we added a count field. The other common solution: never let the queue become completely full. One cell always stays empty, so that:
| Situation | Condition |
|---|---|
| Empty | front == rear |
| Full | (rear + 1) % cap == front |
| Number of elements | (rear - front + cap) % cap |
| i-th element (from 0) | data[(front + i) % cap] |
| Maximum elements | cap - 1 |
rear is again the next free slot. With capacity 5:
SlotQueue s = new SlotQueue(5);
for (int i = 1; i <= 4; i++) s.enqueue(i * 10);
System.out.println(s.raw() + " full=" + s.isFull());
s.dequeue(); s.dequeue(); s.enqueue(50); s.enqueue(60);
System.out.println(s.raw() + " full=" + s.isFull());
[10, 20, 30, 40, 0] front=0 rear=4 size=4 full=true
[60, 20, 30, 40, 50] front=2 rear=1 size=4 full=true
In the first line there is still one free cell in the array but the queue is full: (4 + 1) % 5 = 0 = front. In the second line 50 was written to index 4 and 60 to index 0; rear 1, front 2. The size is (1 − 2 + 5) % 5 = 4.
PITFALL · Why
+ cap? When rear wraps and falls behind front,rear - frontis negative. In Java%leaves a negative result negative: (1 − 4) % 6 = −3. If you add cap first, (1 − 4 + 6) % 6 = 3, the correct size.
A solution pattern for formula questions
cap = 6, front = 4, rear = 1
size = (1 - 4 + 6) % 6 = 3
elements: indices 4, 5, 0
full? (1 + 1) % 6 = 2 ≠ 4 → no
the next enqueue writes to index 1, and rear becomes 2
Deque (double-ended queue)
A queue from which you can insert and remove at both ends. Read as “deck”.
| Operation | Front end | Back end |
|---|---|---|
| Insert | addFirst(x) / offerFirst(x) | addLast(x) / offerLast(x) |
| Remove | removeFirst() / pollFirst() | removeLast() / pollLast() |
| Look | peekFirst() | peekLast() |
If you use only addLast + pollFirst it is a queue, and if you use only addFirst + pollFirst it is a stack. Java’s ArrayDeque class is exactly this: a deque on a circular array.
Deque<Integer> d = new ArrayDeque<>();
d.addFirst(1); d.addLast(2); d.addFirst(3); d.addLast(4);
System.out.println(d);
System.out.println(d.pollLast() + " " + d.pollFirst());
d.offerFirst(9);
System.out.println(d + " peekLast=" + d.peekLast());
[3, 1, 2, 4]
4 3
[9, 1, 2] peekLast=2
Writing a deque with a circular array
front and count are kept. Adding at the end writes to cell (front + count) % cap. Adding at the front moves front back by one:
void addFirst(int x) {
front = (front - 1 + data.length) % data.length;
data[front] = x;
count++;
}
void addLast(int x) {
data[(front + count) % data.length] = x;
count++;
}
int removeLast() {
int idx = (front + count - 1) % data.length;
count--;
return data[idx];
}
With capacity 6: addLast(1), addLast(2), addFirst(3), addFirst(4):
[1, 2, 0, 0, 4, 3] front=4 count=4
When front wraps back from 0 it goes to 5 (3 was written), then to 4 (4 was written). The logical order from front: 4 3 1 2.
The priority queue
Like a hospital emergency room: you are called not by order of arrival but by urgency. Every element has a priority; remove always gives the highest-priority element. In Java, “highest priority” by default means the smallest value.
PriorityQueue<Integer> pq = new PriorityQueue<>();
for (int x : new int[]{5, 1, 8, 3, 9, 2}) pq.add(x);
System.out.println("toString: " + pq);
System.out.println(pq.poll() + " " + pq.poll() + " " + pq.poll() + " size=" + pq.size());
toString: [1, 3, 2, 5, 9, 8]
1 2 3 size=3
The toString output is not sorted. A PriorityQueue keeps a heap inside; the only guarantee is that the one at the front is the smallest. If you want the largest to come out first, give a Comparator:
PriorityQueue<Integer> mx = new PriorityQueue<>(Comparator.reverseOrder());
PriorityQueue<String> byLen = new PriorityQueue<>((a, b) -> a.length() - b.length());
Implementation options
| Structure | insert | removeMin | peekMin |
|---|---|---|---|
| Unsorted array/list | O(1) | O(n) | O(n) |
| Sorted linked list | O(n) | O(1) | O(1) |
| Sorted array | O(n) (shifting) | O(1) (from the end, if sorted descending) | O(1) |
| Binary heap | O(log n) | O(log n) | O(1) |
In this course it is enough for you to be able to write the first two. The sorted list version consists of insertSorted + removeFirst from week 3. The heap is the subject of the next course (SEN2212).
Simulation with queues
The real strength of a queue is in scenarios like “the one whose turn it is gets processed, and the one whose job isn’t finished goes to the back”.
Round-robin scheduling
The operating system distributes the processor among jobs in turn, giving each a fixed time slice (quantum). With time slice 2, jobs A (5 units), B (2), C (4):
while (!names.isEmpty()) {
String p = names.remove();
int run = Math.min(2, left.get(p));
time += run;
left.put(p, left.get(p) - run);
if (left.get(p) > 0) names.add(p); // if unfinished, to the back
}
A[0-2] B[2-4] (B done at 4) C[4-6] A[6-8] C[8-10] (C done at 10) A[10-11] (A done at 11)
Hot potato
Everyone is in a circle. The potato changes hands k times, and whoever is holding it is out of the game. With a queue: k times add(remove()), then one remove(). The queue version of the Josephus problem from week 3.
out: Deniz Cem Ece Bora | winner: Ali
Traversing a queue without damaging it
There is no access by index in a queue. To get the sum without changing the contents, rotate size times:
static int sumQueue(Queue<Integer> q) {
int sum = 0, n = q.size();
for (int i = 0; i < n; i++) { int x = q.remove(); sum += x; q.add(x); }
return sum;
}
For [3, 6, 9, 12] it returns 30 and the queue stays in the same order. It is important to take the loop bound into a variable n beforehand; i < q.size() also works in this example because the size doesn’t change, but it misleads in variations that remove elements.
Common mistakes
| Wrong | Correct |
|---|---|
Size is rear - front | (rear - front + cap) % cap |
| cap elements in a queue without count | At most cap − 1 |
front = (front - 1) % cap | (front - 1 + cap) % cap |
Full condition rear == front | That is the empty condition; full: (rear + 1) % cap == front |
| PriorityQueue is FIFO | By priority; in Java the smallest first |
System.out.println(pq) prints sorted | Heap order; take out with poll |
| A separate class is needed for the largest first | Comparator.reverseOrder() |
| Putting a finished job back in the queue in a simulation | Only those with remaining > 0 go to the back |
Test yourself
Exam-style questions. Solve them on paper first, then reveal the answer.
A circular queue (no count field, one slot always left empty) has capacity 6, front = 4, rear = 1 (rear = next free slot). How many elements are in the queue?
Show answer
The size formula is . The elements are in cells 4, 5, 0. The + cap prevents a negative result.
Same design, capacity 5, front = 2, rear = 1. Is the queue full? How many elements can this queue hold at most?
Show answer
The full condition is : , and front is also 2: full. Since the empty condition is front == rear, one cell is always left empty; in 5 cells at most 4 elements. This is the price of telling empty from full without keeping count.
In a circular queue with capacity 6 and front = 4, at which array index is the third element from the front (i = 2)?
Show answer
The i-th element is in cell : . From the front: index 4, 5, 0.
A circular-array deque does front = (front - 1) % cap in addFirst. With cap = 8 and front = 0, what goes wrong, and what is the correct formula?
Show answer
In Java the sign of the % result comes from the left operand: (0 − 1) % 8 = −1. data[-1] throws an ArrayIndexOutOfBoundsException. Add cap before taking the remainder: (0 − 1 + 8) % 8 = 7. Wrapping backward always needs + cap.
What is printed?
Deque<Integer> d = new ArrayDeque<>();
d.addFirst(1); d.addLast(2); d.addFirst(3); d.addLast(4);
System.out.println(d);
System.out.println(d.pollLast() + " " + d.pollFirst());
d.offerFirst(9);
System.out.println(d + " peekLast=" + d.peekLast());Show answer
[1] → [1,2] → [3,1,2] → [3,1,2,4]. pollLast 4, pollFirst 3: [1,2]. offerFirst(9): [9,1,2]. The last element is 2. A deque can insert and remove at both ends; toString writes from front to back.
Write static boolean isPalindrome(String s) using a Deque<Character>: compare characters from both ends.
Show answer
static boolean isPalindrome(String s) {
Deque<Character> d = new ArrayDeque<>();
for (char c : s.toCharArray()) d.addLast(c);
while (d.size() > 1) {
if (d.pollFirst() != d.pollLast()) return false;
}
return true;
}"racecar" → true, "radix" → false (r is not x), "abba" → true, a single-letter "a" → true (the loop never runs). Note: when Character objects are compared with !=, it works here because Java serves small characters from a cache; the safe way is !d.pollFirst().equals(d.pollLast()).What is printed?
PriorityQueue<Integer> pq = new PriorityQueue<>();
for (int x : new int[]{5, 1, 8, 3, 9, 2}) pq.add(x);
System.out.println(pq.poll() + " " + pq.poll() + " " + pq.poll() + " size=" + pq.size());Show answer
Java's PriorityQueue by default gives the smallest priority first (min-priority). The insertion order doesn't matter: poll gives 1, 2, 3 in turn, and 3 elements remain (5, 8, 9).
Same queue right after the six add calls: what does System.out.println(pq) print, and why is it not sorted?
Show answer
PriorityQueue keeps a heap (a heap tree, a SEN2212 topic) inside. toString writes the array in its internal order; only the first element is guaranteed to be the smallest. If you want to see it sorted, take the elements out one by one with poll.
What is printed?
PriorityQueue<Integer> mx = new PriorityQueue<>(Comparator.reverseOrder());
mx.add(4); mx.add(10); mx.add(7);
System.out.println(mx.peek());
mx.poll(); mx.add(6);
while (!mx.isEmpty()) System.out.print(mx.poll() + " ");Show answer
With reverseOrder the largest comes first (max-priority). peek is 10. poll removes 10, 6 is added: {4, 7, 6}. From largest to smallest: 7 6 4.
A priority queue (smallest first) is implemented with (a) an unsorted linked list, (b) a sorted linked list. Give the time complexity of insert and removeMin for each.
Show answer
In an unsorted list, inserting at the head is O(1), but finding the smallest requires traversing everything: O(n). In a sorted list, finding the right place when inserting is O(n) (insertSorted in week 3), and the smallest is always at the head: O(1). With a heap both become O(log n).
Round-robin CPU scheduling with time quantum 2. Jobs enter the queue in order A (needs 5 units), B (2), C (4). A job runs for min(2, remaining) units; if unfinished it goes to the back. At what time does each job finish?
Show answer
time running after run queue
0-2 A A:3 B C A
2-4 B B:0 done (4) C A
4-6 C C:2 A C
6-8 A A:1 C A
8-10 C C:0 done (10) A
10-11 A A:0 done (11)Program output: A[0-2] B[2-4] (B done at 4) C[4-6] A[6-8] C[8-10] (C done at 10) A[10-11] (A done at 11).Hot potato: the queue holds Ali Bora Cem Deniz Ece (front first). Each round, move the front person to the back 3 times, then remove the front person. Who is removed in order, and who wins?
Show answer
Round 1: Ali, Bora, Cem go to the back → Deniz Ece Ali Bora Cem, Deniz is removed. Round 2: Ece, Ali, Bora go to the back → Cem Ece Ali Bora, Cem is removed. Round 3: with three people, three rotations return to the same order: Ece Ali Bora, Ece is removed. Round 4: Ali Bora; Ali, Bora, Ali go to the back → Bora Ali, Bora is removed. Remaining: Ali.
Verification: The formulas, the behaviour of the circular deque, ArrayDeque and PriorityQueue, and two simulations were run on the JDK in verify/SEN2211/W08.java (java W08.java). Java's negative mod result (-1) and the toString output of PriorityQueue were also taken from the program.