Stacks & Queues · 24 of 38
Implement n queue in an array
Let several waiting lines share one row of seats, where any line can take any empty seat and each seat remembers who sits behind it.
- Simple way
- time O(1) per operation
room O(n + k), but each queue gets only n/k - Clever way
- time O(1) per operation
room O(n + k)
The problem, simply
You get one array of n boxes. Build k queues inside it, with enqueue(q, x) and dequeue(q) for any queue number q.
The rule: no queue may say "full" while some box anywhere in the array is empty. If one queue is busy and the others are quiet, the busy one should be able to use almost all the boxes.
A story to remember it
Picture it
Six boxes shared by three queues. Queue 0 is 20 then 50, queue 1 is 30 then 60, queue 2 is 40. next says who stands behind each box in its queue. front and rear hold one box number per queue.
Examples
A normal run
- In
n = 6, k = 3: enqueue(0,10), enqueue(0,20), enqueue(1,30), enqueue(2,40), enqueue(0,50), dequeue(0)- Out
10
10 joined queue 0 first, so it leaves first. Queues 1 and 2 are untouched.
A freed box is reused
- In
the run above, then enqueue(1,60)- Out
60 goes into box 0
Box 0 was freed when 10 left. It is the first empty box, so queue 1 takes it, even though queue 0 used it before.
One queue uses everything
- In
n = 3, k = 3: enqueue(2,1), enqueue(2,2), enqueue(2,3), dequeue(2)- Out
all pushes work, dequeue = 1
Queue 2 takes all three boxes. With fixed blocks of n/k = 1 box, the second enqueue would fail.
Room again, and an empty queue
- In
after the example above: enqueue(0,9), dequeue(1)- Out
enqueue works into box 0; dequeue(1) fails, queue 1 is empty
The dequeue freed one box, so there is room again. Queue 1 never got anything.
Way 1: the simple way
Cut the array into k blocks of n/k boxes. Queue q owns block q and runs as a small circular queue inside it.
- For queue
q, the block starts atstart = q * size. - Keep
head[q](an offset inside the block) andcount[q]. - Enqueue writes at
start + (head[q] + count[q]) % size. Dequeue reads atstart + head[q]and moveshead[q]forward with% size.
n = 6, k = 3, so each queue owns 2 boxes. Queue 0 is full after two values, while boxes 2 to 5 sit empty.
- rejected
- ignored
class KQueuesBlocks {
private final int[] arr, head, count;
private final int size; // boxes per queue
KQueuesBlocks(int n, int k) {
arr = new int[n];
head = new int[k];
count = new int[k];
size = n / k;
}
boolean enqueue(int q, int x) {
if (count[q] == size) return false; // this block is full
arr[q * size + (head[q] + count[q]) % size] = x;
count[q]++;
return true;
}
int dequeue(int q) {
if (count[q] == 0) throw new RuntimeException("queue " + q + " is empty");
int x = arr[q * size + head[q]];
head[q] = (head[q] + 1) % size;
count[q]--;
return x;
}
}How long it takes: O(1) per action. Extra room: the array plus three numbers per queue, O(n + k). But a queue is full after n/k values even when the rest of the array is empty.
Way 2: the clever way
Turn every box into a link in a chain, using an extra array next.
arr[i]: the value in boxi.next[i]: for a used box, the box behind it in the same queue, or-1if it is the last one. For an empty box, the next empty box.front[q]andrear[q]: the first and last box of queueq, or-1if empty.free: the first empty box. At the start the empty chain is0, 1, 2, ....
enqueue(q, x): iffree == -1, overflow. Takei = free, movefree = next[i]. Writearr[i] = xandnext[i] = -1. If queueqis empty,front[q] = i; otherwise hook it on withnext[rear[q]] = i. Thenrear[q] = i.dequeue(q): iffront[q] == -1, it is empty. Takei = front[q]andx = arr[i]. Movefront[q] = next[i]; if that is-1, also setrear[q] = -1. Give boxiback:next[i] = free,free = i. Returnx.
Each queue is a linked list living inside the array, and the empty boxes are one more list.
Three queues sharing six boxes
01 / 09
Start. All boxes are empty, chained 0, 1, 2, 3, 4, 5. Every queue has front and rear -1.
- looking now
- done
- rejected
- just added
class KQueues {
private final int[] arr, next, front, rear;
private int free; // first empty box, -1 if none
KQueues(int n, int k) {
arr = new int[n];
next = new int[n];
front = new int[k];
rear = new int[k];
Arrays.fill(front, -1);
Arrays.fill(rear, -1);
for (int i = 0; i < n - 1; i++) next[i] = i + 1; // chain the empty boxes
next[n - 1] = -1;
free = 0;
}
boolean enqueue(int q, int x) {
if (free == -1) return false; // every box is used
int i = free;
free = next[i]; // take box i off the empty chain
arr[i] = x;
next[i] = -1; // nobody behind it yet
if (front[q] == -1) front[q] = i; // first in an empty queue
else next[rear[q]] = i; // hook on behind the old rear
rear[q] = i;
return true;
}
int dequeue(int q) {
if (front[q] == -1) throw new RuntimeException("queue " + q + " is empty");
int i = front[q];
int x = arr[i];
front[q] = next[i]; // the one behind moves up
if (front[q] == -1) rear[q] = -1; // queue is empty now
next[i] = free; // box i goes back on the empty chain
free = i;
return x;
}
boolean isEmpty(int q) { return front[q] == -1; }
boolean isFull() { return free == -1; }
}How long it takes: every action changes a handful of numbers, O(1). Extra room: arr and next of size n, and front and rear of size k, so O(n + k).
Watch out for
- In
dequeue, readnext[i]intofront[q]before you overwritenext[i]withfree. - When a queue becomes empty, reset
rear[q]too. Otherwise the next enqueue hooks onto a box that now belongs to the empty chain. - In
enqueue, readfree = next[i]before changingnext[i]. - An empty queue is
front[q] == -1. Do not userear[q]for that unless you keep both in sync.
What interviewers ask next
- How is this different from
kstacks in one array? Stacks add and remove at the same end, so they need onlytop[s]. Queues add at the rear and remove at the front, so they need bothfront[q]andrear[q]. - Can you add
size(q)inO(1)? Keep acount[q]array and update it in both actions. - Why do this instead of
klinked lists? Real linked lists allocate memory for every node. Here all memory is one block made up front, which suits embedded systems and memory pools. - Can one queue be given a guaranteed minimum? Reserve
mboxes per queue up front, and share the rest through the free chain.
Remember this
Grown-up words: the seat cards are the next array (an index based linked list), the usher's chain of empty seats is a free list, front[q] and rear[q] are the head and tail of queue q, and the fixed blocks in Way 1 are a static partition.