SEN2211 · Week 8 · Queues II: Circular and Priority Queues

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.

circular queueone empty slotmodular arithmeticdequedouble-ended queueaddFirstpollLastpriority queuemin-priorityPriorityQueueComparatorround-robin schedulingsimulation

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:

  1. In a circular index everything is % cap. When going backward, add + cap first.
  2. If there is no count, one cell stays empty. Empty: front == rear. Full: (rear + 1) % cap == front.
  3. A deque is a double-ended queue. It can be used both like a stack and like a queue.
  4. 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:

SituationCondition
Emptyfront == rear
Full(rear + 1) % cap == front
Number of elements(rear - front + cap) % cap
i-th element (from 0)data[(front + i) % cap]
Maximum elementscap - 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 - front is 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”.

OperationFront endBack end
InsertaddFirst(x) / offerFirst(x)addLast(x) / offerLast(x)
RemoveremoveFirst() / pollFirst()removeLast() / pollLast()
LookpeekFirst()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

StructureinsertremoveMinpeekMin
Unsorted array/listO(1)O(n)O(n)
Sorted linked listO(n)O(1)O(1)
Sorted arrayO(n) (shifting)O(1) (from the end, if sorted descending)O(1)
Binary heapO(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

WrongCorrect
Size is rear - front(rear - front + cap) % cap
cap elements in a queue without countAt most cap − 1
front = (front - 1) % cap(front - 1 + cap) % cap
Full condition rear == frontThat is the empty condition; full: (rear + 1) % cap == front
PriorityQueue is FIFOBy priority; in Java the smallest first
System.out.println(pq) prints sortedHeap order; take out with poll
A separate class is needed for the largest firstComparator.reverseOrder()
Putting a finished job back in the queue in a simulationOnly those with remaining > 0 go to the back

Test yourself

Exam-style questions. Solve them on paper first, then reveal the answer.

Question 1easy

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
3

The size formula is (rear−front+cap)%cap=(1−4+6)%6=3(rear - front + cap) \% cap = (1 - 4 + 6) \% 6 = 3. The elements are in cells 4, 5, 0. The + cap prevents a negative result.

Question 2medium

Same design, capacity 5, front = 2, rear = 1. Is the queue full? How many elements can this queue hold at most?

Show answer
Yes, full; at most 4

The full condition is (rear+1)%cap==front(rear + 1) \% cap == front: (1+1)%5=2(1 + 1) \% 5 = 2, 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.

Question 3easy

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
0

The i-th element is in cell (front+i)%cap(front + i) \% cap: (4+2)%6=0(4 + 2) \% 6 = 0. From the front: index 4, 5, 0.

Question 4medium

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
Java gives -1 (index out of bounds); use (front - 1 + cap) % cap = 7

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.

Question 5medium

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
[3, 1, 2, 4] / 4 3 / [9, 1, 2] peekLast=2

[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.

Question 6hard

Write static boolean isPalindrome(String s) using a Deque<Character>: compare characters from both ends.

Show answer
Add all chars with addLast; while size > 1, compare pollFirst and pollLast.

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()).

Question 7easy

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
1 2 3 size=3

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).

Question 8hard

Same queue right after the six add calls: what does System.out.println(pq) print, and why is it not sorted?

Show answer
[1, 3, 2, 5, 9, 8]

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.

Question 9medium

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
10 / 7 6 4

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.

Question 10easy

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
(a) insert O(1), removeMin O(n); (b) insert O(n), removeMin O(1)

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).

Question 11hard

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
B at 4, C at 10, A at 11

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).

Question 12hard

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
Deniz, Cem, Ece, Bora; winner Ali

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.

Review card

CIRCULAR QUEUE WITHOUT COUNT (one cell empty): empty: front == rear full: (rear + 1) % cap == front capacity = cap - 1 size = (rear - front + cap) % cap i-th element: data[(front + i) % cap] Wrapping backward: (i - 1 + cap) % cap (in Java (0-1)%8 = -1 !) DEQUE (double-ended): addFirst/addLast, pollFirst/pollLast, peekFirst/peekLast all O(1) a deque can be both a stack and a queue; palindrome: compare from both ends PRIORITY QUEUE: comes out by PRIORITY, not by order Java PriorityQueue: SMALLEST first; for the largest use new PriorityQueue<>(Comparator.reverseOrder()) toString is NOT sorted (heap order); to see it sorted use poll unsorted list: insert O(1), removeMin O(n) | sorted list: insert O(n), removeMin O(1) | heap: both O(log n) SIMULATION: take from the queue, process, put back at the end if unfinished (round-robin, hot potato) Trace table: time | running | remaining | queue