Chapter 8: Queues and Deques

Data Structures
FIFOenqueue / dequeueArray-based queueCircular queue Linked queueDequeSliding windowRound robin
Sercan KÜLCÜ · Giresun University · Computer Engineering

Introduction

Learning Outcomes

The FIFO model

Explain the first-in, first-out behavior of a queue.

Implementation

Implement a queue with an array and with a linked list.

Kinds of queues

Tell a circular queue from a double-ended queue (deque) and use each where it fits.

Applications

Choose the right kind of queue for real-world problems.

The Queue ADT

The Queue

  • A linear data structure that is open at both ends.
  • Items leave in the order they arrived: first in, first out (FIFO).
  • Items join at one end (the rear) and leave from the other (the front).
  • Think of the line at a checkout or a ticket window: whoever arrives first is served first.

The Queue ADT

Basic Queue Operations

EnqueueAdds a new item at the rear of the queue.Time: O(1)
DequeueRemoves and returns the item at the front. The first item in is the first out (FIFO).Time: O(1)
Items in the middle are never accessed directly; only the front and rear are used.
← frontrear

Horses walking single file: the one in front passes first, and newcomers join at the back.

The Queue ADT

The Queue Abstract Data Type

OperationDescription
enqueue(e)adds e at the rear
dequeue()removes and returns the front item
first() / peek()returns the front item without removing it
rear()returns the rear item without removing it
size()number of items
isEmpty()is the queue empty?
isFull()is it full? (fixed capacity only)
public interface Queue<E> {
    int size();
    boolean isEmpty();
    void enqueue(E e);
    E first();    // null if empty
    E dequeue();  // null if empty
}
Dequeuing from an empty queue is an underflow; enqueuing into a full one, an overflow. Goodrich's interface returns null when the queue is empty; some implementations throw an exception.

The Queue ADT

Queue Operations, Step by Step

OutInemptyfront = rear = null

Initially the queue is empty: front and rear point to nothing (null).

OutInenqueue(20)20frontrearsize = 1

The first item goes in: a lone item is both the front and the rear.

OutInenqueue(10)2010frontrearsize = 2

10 joins at the rear: rear advances, front stays put.

OutInenqueue(15)201015frontrearsize = 3

15 joins at the rear. Queue: 20, 10, 15 (front to rear).

OutIndequeue() → 201015frontrear20removedsize = 2

20 leaves from the front; front advances to the next item (10).

OutIndequeue() → 1015frontrear10removedsize = 1

10 leaves. Now front and rear point to the same item (15).

OutIndequeue() → 15empty15removedfront = rear = null

When the last item leaves, both become null. Items left in the order 20, 10, 15, the order they arrived (FIFO).

The Queue ADT

Queues in Java: java.util.Queue

OperationThrows an exceptionReturns a special value
Insertadd(e)offer(e) → false
Removeremove()poll() → null
Examineelement()peek() → null
  • ArrayDeque: backed by a circular array; usually the fastest choice.
  • LinkedList: backed by a linked list.
Queue<Integer> q = new ArrayDeque<>();
q.offer(20);
q.offer(10);
q.offer(15);        // [20, 10, 15]
q.peek();           // 20, not removed
q.poll();           // 20 → [10, 15]
q.poll();           // 10 → [15]
q.size();           // 1
q.poll();           // 15 → []
q.poll();           // null (empty)
Our own classes use the names enqueue/dequeue; the Java library uses offer/poll/peek.

The Queue ADT

Where Queues Are Used

Producerkeys, net, processenqueueJob 5Job 4Job 3Job 2Job 1buffer queue (FIFO)rearfrontdequeueConsumerCPU, printer
Operating System SchedulerReady processes wait in the ready queue and get the CPU in turn.
Print QueueDocuments sent to a printer are printed in the order they arrive.
BuffersKeyboard input, network packets, video streams: a buffer evens out the speed difference between producer and consumer.
Breadth-First Search (BFS)Discovered nodes wait their turn in a queue, so the graph is explored level by level.

Array-Based Queues

A Queue in an Array

  • The items live in an array, and two indices mark the ends of the queue:
    • front: index of the first item.
    • rear: index of the last item.
  • Enqueue increments rear; dequeue increments front. Items never move.
  • Simple and fast, but the array has a fixed capacity.
020110215345frontrearN = 6

The item at index 0 was dequeued earlier. Its slot is free now, but a simple array queue cannot reuse it.

Array-Based Queues

The Trouble with a Simple Array Queue

012345empty: front = 0, rear = −1

Empty queue, capacity N = 6. rear = −1: no items yet.

01012345front = rearenqueue(10) → rear = 0

enqueue(10): rear++ → 0 → data[0].

0101202345frontrearenqueue(20) → rear = 1

enqueue(20): rear++ → 1 → data[1].

010120230345frontrearenqueue(30) → rear = 2

enqueue(30): rear++ → 2 → data[2].

01012023034045frontrearenqueue(40) → rear = 3

enqueue(40): rear++ → 3 → data[3].

012023034045frontreardequeue() → 10, front = 1

dequeue() → 10: front++ → 1. The freed slot is never reused.

0123034045frontreardequeue() → 20, front = 2

dequeue() → 20: front++ → 2. The freed slot is never reused.

01234045front = reardequeue() → 30, front = 3

dequeue() → 30: front++ → 3. The freed slot is never reused.

0123404505frontrearenqueue(50) → rear = 4

enqueue(50): rear = 4.

012340450560frontrearenqueue(60) → rear = 5

enqueue(60): rear = 5 = N − 1, the last index of the array.

01234045056070?frontrearenqueue(70): rear + 1 = 6 = N → “full”!

“Full” with only 3 items: the 3 orange slots are wasted. Shifting costs O(n); the fix is a circular queue.

Fix 1: ShiftingShift the items left on every dequeue → dequeue becomes O(n).
Fix 2: A circular arrayIndices wrap around to the start → every operation is O(1).

Array-Based Queues

The Circular Queue

  • FIFO like an ordinary queue, but after the last slot of the array comes the first slot again.
  • The two ends of the array are logically joined into a ring.
  • Indices advance modulo N: (i + 1) % N.
  • Freed slots are reused, so items never need shifting.
  • Also known as a ring buffer.
100201302403504567frontrear

Here rear is the free slot where the next item will go: rear = (front + size) % N = (0 + 5) % 8 = 5.

Array-Based Queues

Moving the Indices with Mod

WhatFormulaExample: N = 8, front = 6, size = 4
Next free slot (where to insert)(front + size) % N(6 + 4) % 8 = 2
Last item(front + size − 1) % N(6 + 3) % 8 = 1
k-th item from the front (k = 0, 1, …)(front + k) % Nk = 2 → (6 + 2) % 8 = 0
front after a dequeue(front + 1) % N(6 + 1) % 8 = 7
C0D12345A6B7▲ front▲ rearlast itemthen wraps to 0 →
Front to rear: A (6), B (7), C (0), D (1). The mod turns the jump from the end of the array back to the start into one expression.

Array-Based Queues

Full or Empty?

Empty0123▲ front▲ rearfront == rearFullE0B1C2D3▲ front▲ rearfront == rear (!)One slot empty0B1C2D3▲ front▲ rear(rear + 1) % N == front

With only the front and rear indices, an empty and a full queue look the same: both indices point to the same slot. Two common fixes:

1. An item counter (size)Empty: size == 0 · Full: size == N
All N slots are used. This is the method used in this course.
2. Leave one slot emptyEmpty: front == rear · Full: (rear + 1) % N == front
No counter is needed, but at most N − 1 items fit.

Array-Based Queues

Circular Queue, Step by Step (N = 5)

0123401234▲front, availfront = 0size = 0avail = (front + size) % 5 = (0 + 0) % 5 = 0 · size = 0 → EMPTY

Empty queue: size = 0. On the ring, the red-edged slot is front; in the array, ▲avail marks the next write position.

10012340101234▲front▲availfront = 0size = 1write slot = (0 + 0) % 5 = 0

enqueue(10) → data[0]; size = 1.

100201234010120234▲front▲availfront = 0size = 2write slot = (0 + 1) % 5 = 1

enqueue(20) → data[1]; size = 2.

1002013023401012023034▲front▲availfront = 0size = 3write slot = (0 + 2) % 5 = 2

enqueue(30) → data[2]; size = 3.

020130234012023034▲front▲availfront = 1size = 2front = (0 + 1) % 5 = 1

dequeue() → 10; front = (0 + 1) % 5 = 1. The items do not move.

01302340123034▲front▲availfront = 2size = 1front = (1 + 1) % 5 = 2

dequeue() → 20; front = (1 + 1) % 5 = 2. The items do not move.

013024034012303404▲front▲availfront = 2size = 2write slot = (2 + 1) % 5 = 3

enqueue(40) → data[3].

0130240350401230340450▲front▲availfront = 2size = 3write slot = (2 + 2) % 5 = 4

enqueue(50) → data[4].

60013024035040601230340450▲front▲availfront = 2size = 4write slot = (2 + 3) % 5 = 0

enqueue(60) → data[0]: the index wraps around, reusing a slot that a simple array queue would call “full.”

600701302403504060170230340450▲front, availfront = 2size = 5write slot = (2 + 4) % 5 = 1

enqueue(70) → data[1]. size == N → full; front == avail, but thanks to size the queue is not mistaken for empty.

60070124035040601702340450▲front▲availfront = 3size = 4front = (2 + 1) % 5 = 3

dequeue() → 30; front = 3.

6007012350406017023450▲front▲availfront = 4size = 3front = (3 + 1) % 5 = 4

dequeue() → 40; front = 4.

600701234060170234▲front▲availfront = 0size = 2front = (4 + 1) % 5 = 0

dequeue() → 50; front = 0. (4 + 1) % 5 = 0, so front wraps around too.

07012340170234▲front▲availfront = 1size = 1front = (0 + 1) % 5 = 1

dequeue() → 60; front = 1.

0123401234▲front, availfront = 2size = 0front = (1 + 1) % 5 = 2

dequeue() → 70; front = 2. size = 0 → the queue is empty again. Items left in the order 30, 40, 50, 60, 70, the order they arrived (FIFO).

Array-Based Queues

Circular Queue: Class and Status Queries

public class CircularQueue<E> {
    private E[] data;       // stores the items
    private int front = 0;    // index of the first item
    private int size = 0;  // number of items

    @SuppressWarnings("unchecked")
    public CircularQueue(int capacity) {
        data = (E[]) new Object[capacity];
    }
    public int size()      { return size; }
    public boolean isEmpty()  { return size == 0; }
    public boolean isFull() { return size == data.length; }
    public E first()          { return isEmpty() ? null : data[front]; }
    // enqueue(), dequeue()
}
The rear index is not stored but computed when needed as (front + size) % data.length, so two fields can never disagree.

Array-Based Queues

Circular Queue: enqueue and dequeue

public void enqueue(E item) {           // enqueue
    if (isFull())
        throw new IllegalStateException("full");
    int avail = (front + size) % data.length;
    data[avail] = item;
    size++;
}
public E dequeue() {                  // dequeue
    if (isEmpty())
        throw new IllegalStateException("empty");
    E item = data[front];
    data[front] = null;               // drop the reference
    front = (front + 1) % data.length;
    size--;
    return item;
}
Where to insertN = 5, front = 3, size = 2avail = (3 + 2) % 5 = 0
Advancing the frontN = 5, front = 4front = (4 + 1) % 5 = 0
CostBoth operations take a constant number of steps: O(1). No loops, no shifting.

Array-Based Queues · Step through the code

Code Trace: Circular enqueue()

013024034012303404▲front▲availfront = 2size = 2

Start: data.length = 5, front = 2, size = 2 (30 and 40). Next we enqueue 50, 60, 70, 80.

013024034012303404▲front▲availfront = 2size = 2

enqueue(50): isFull() → size 2 == 5? no.

013024034012303404▲front▲availfront = 2size = 2

avail = (2 + 2) % 5 = 4 % 5 = 4.

0130240350401230340450▲frontfront = 2size = 2

data[4] = 50. The other items do not move.

0130240350401230340450▲front▲availfront = 2size = 3

size++ → 3. front is unchanged (data[2]).

0130240350401230340450▲front▲availfront = 2size = 3

enqueue(60): isFull() → size 3 == 5? no.

0130240350401230340450▲front▲availfront = 2size = 3

avail = (2 + 3) % 5 = 5 % 5 = 0 → past the end of the array, the index wraps around.

60013024035040601230340450▲frontfront = 2size = 3

data[0] = 60. The other items do not move.

60013024035040601230340450▲front▲availfront = 2size = 4

size++ → 4. front is unchanged (data[2]).

60013024035040601230340450▲front▲availfront = 2size = 4

enqueue(70): isFull() → size 4 == 5? no.

60013024035040601230340450▲front▲availfront = 2size = 4

avail = (2 + 4) % 5 = 6 % 5 = 1 → past the end of the array, the index wraps around.

600701302403504060170230340450▲frontfront = 2size = 4

data[1] = 70. The other items do not move.

600701302403504060170230340450▲front, availfront = 2size = 5

size++ → 5. front is unchanged (data[2]). The queue is now full.

600701302403504060170230340450▲front, availfront = 2size = 5

enqueue(80): isFull() → size 5 == 5? yes.

600701302403504060170230340450▲front, availfront = 2size = 5

The queue is full → IllegalStateException("full") is thrown and the array is unchanged. All 5 slots were used; none was wasted.

public void enqueue(E item) {
  if (isFull())
    throw new IllegalStateException("full");
  int avail = (front + size) % data.length;
  data[avail] = item;
  size++;
}

Array-Based Queues · Step through the code

Code Trace: Circular dequeue()

600701302403504060170230340450▲front, availfront = 2size = 5

Start: a full queue, front = 2. Front to rear: 30, 40, 50, 60, 70. We call dequeue() three times.

600701302403504060170230340450▲front, availfront = 2size = 5

isEmpty(): size = 5 ≠ 0 → not empty.

600701302403504060170230340450▲front, availfront = 2size = 5

item = data[2] = 30: the front (oldest) item.

60070124035040601702340450▲front, availfront = 2size = 5

data[2] = null: the slot is cleared, so the garbage collector can reclaim the object.

60070124035040601702340450▲front, availfront = 3size = 5

front = (2 + 1) % 5 = 3.

60070124035040601702340450▲front▲availfront = 3size = 4

size-- → 4; return 30.

60070124035040601702340450▲front▲availfront = 3size = 4

isEmpty(): size = 4 ≠ 0 → not empty.

60070124035040601702340450▲front▲availfront = 3size = 4

item = data[3] = 40: the front (oldest) item.

6007012350406017023450▲front▲availfront = 3size = 4

data[3] = null: the slot is cleared, so the garbage collector can reclaim the object.

6007012350406017023450▲front▲availfront = 4size = 4

front = (3 + 1) % 5 = 4.

6007012350406017023450▲front▲availfront = 4size = 3

size-- → 3; return 40.

6007012350406017023450▲front▲availfront = 4size = 3

isEmpty(): size = 3 ≠ 0 → not empty.

6007012350406017023450▲front▲availfront = 4size = 3

item = data[4] = 50: the front (oldest) item.

600701234060170234▲front▲availfront = 4size = 3

data[4] = null: the slot is cleared, so the garbage collector can reclaim the object.

600701234060170234▲front▲availfront = 0size = 3

front = (4 + 1) % 5 = 0 → 5 % 5 = 0: front wraps around.

600701234060170234▲front▲availfront = 0size = 2

size-- → 2; return 50. A constant number of steps: O(1).

public E dequeue() {
  if (isEmpty())
    throw new IllegalStateException("empty");
  E item = data[front];
  data[front] = null;           // drop the reference
  front = (front + 1) % data.length;
  size--;
  return item;
}

Array-Based Queues · Step through the code

Code Trace: Growing a Full Circular Queue

data0C1D2 ▼frontA3Bfront = 2 · size = 4 · N = 4order: A B C D

Full queue (front = 2): the order is A, B, C, D, but the array holds C, D, A, B. Now enqueue(E).

data0C1D2 ▼frontA3Bfront = 2 · size = 4 · N = 4full!

4 == 4 → full. Instead of failing, grow to twice the capacity.

data0C1D2 ▼frontA3Btempfront = 2 · size = 4 · N = 4

A new array of 8 cells is allocated.

data0C1D2 ▼frontA3BtempAfront = 2 · size = 4 · N = 4

k = 0: temp[0] = data[(2 + 0) % 4] = data[2] = A.

data0C1D2 ▼frontA3BtempABfront = 2 · size = 4 · N = 4

k = 1: temp[1] = data[(2 + 1) % 4] = data[3] = B.

data0C1D2 ▼frontA3BtempABCfront = 2 · size = 4 · N = 4

k = 2: temp[2] = data[(2 + 2) % 4] = data[0] = C. The mod wrapped around to the start.

data0C1D2 ▼frontA3BtempABCDfront = 2 · size = 4 · N = 4

k = 3: temp[3] = data[(2 + 3) % 4] = data[1] = D.

data0A1B2 ▼frontC3D4567front = 2 · size = 4 · N = 8front not updated yet!

data = temp: the items are now in order (Arrays.copyOf would have kept them scrambled).

data0 ▼frontA1B2C3D4567front = 0 · size = 4 · N = 8

front = 0: the front item (A) is now in data[0].

data0 ▼frontA1B2C3D4E567front = 0 · size = 4 · N = 8

Back in enqueue: (0 + 4) % 8 = 4 → data[4] = E.

data0 ▼frontA1B2C3D4E567front = 0 · size = 5 · N = 8

size → 5. Growing costs O(n), but because the capacity doubles, enqueue is amortized O(1).

private void resize(int newCap) {
  E[] temp = (E[]) new Object[newCap];
  for (int k = 0; k < size; k++)
    temp[k] = data[(front + k) % data.length];
  data = temp;
  front = 0;
}
public void enqueue(E item) {
  if (size == data.length) resize(2 * data.length);
  data[(front + size) % data.length] = item;
  size++;
}

Linked Queues

A Queue in a Linked List

  • Each queue item is a node (data plus a next reference).
  • front marks the head and rear the tail: insert at the tail, remove at the head.
  • The size is dynamic; there is no capacity limit (no full queue).
  • Each node costs one extra reference and an object allocation.
class Node {
    int data;
    Node next;
    Node(int data) {
        this.data = data;
    }
}
// fields:
Node front, rear;
int size;
108115nullfrontrear

Why insert at the tail and remove at the head? In a singly linked list, removing the head is O(1), but removing the tail means finding its predecessor: O(n).

Linked Queues

Enqueue and Dequeue Algorithms

enqueue(x)
  1. Is the queue full? (fixed-capacity array only)
  2. If so, report an overflow and stop.
  3. Otherwise, write x at the rear position.
  4. Advance the rear to the next position.
  5. Increment the item count and report success.
dequeue()
  1. Is the queue empty?
  2. If so, report an underflow and stop.
  3. Otherwise, take the front item.
  4. Advance the front to the next item.
  5. Decrement the item count and return the item.
StepCircular arrayLinked list
Write at the reardata[(front + size) % N] = xrear.next = temp; rear = temp
Advance the frontfront = (front + 1) % Nfront = front.next
Full checksize == Nnot needed (while memory lasts)

Linked Queues · Step through the code

Code Trace: Linked Queue enqueue

front = null · rear = null · size = 0

Empty queue: front = rear = null. We call enqueue(10), enqueue(15), enqueue(20) in turn.

10tempfront = null · rear = null · size = 0

new Node(10): a new node with next = null, not yet in the list.

10tempfront = null · rear = null · size = 0

isEmpty() → true (size 0).

10front, temprear = null · size = 0

First node: front = temp.

10front, temprearsize = 0

rear = temp: rear is now the new node. The lone node is both front and rear.

10front, temprearsize = 1

size++ → 1.

1015fronttemprearsize = 1

new Node(15): a new node with next = null, not yet in the list.

1015fronttemprearsize = 1

isEmpty() → false; take the else branch.

1015fronttemprearsize = 1

rear.next = temp: the old rear (10) links to the new node.

1015fronttemprearsize = 1

rear = temp: rear is now the new node.

1015fronttemprearsize = 2

size++ → 2.

101520fronttemprearsize = 2

new Node(20): a new node with next = null, not yet in the list.

101520fronttemprearsize = 2

isEmpty() → false; take the else branch.

101520fronttemprearsize = 2

rear.next = temp: the old rear (15) links to the new node.

101520fronttemprearsize = 2

rear = temp: rear is now the new node.

101520fronttemprearsize = 3

size++ → 3. The list was never traversed: each enqueue is O(1).

public void enqueue(int data) {
    Node temp = new Node(data);
    if (isEmpty()) {
        front = temp;
    } else {
        rear.next = temp;
    }
    rear = temp;
    size++;
}

Linked Queues · Step through the code

Code Trace: Linked Queue dequeue

101520frontrearsize = 3

Queue: 10, 15, 20. We call dequeue() three times.

101520frontrearsize = 3

isEmpty() → false (size 3).

101520frontrearsize = 3

result = front.data = 10.

101520frontrearsize = 3

front = front.next → node(15). Nothing refers to the old node now; the garbage collector reclaims it.

101520frontrearsize = 3

front == null → false; rear stays the same.

1520frontrearsize = 2

size-- → 2; return 10.

1520frontrearsize = 2

isEmpty() → false (size 2).

1520frontrearsize = 2

result = front.data = 15.

1520frontrearsize = 2

front = front.next → node(20). Nothing refers to the old node now; the garbage collector reclaims it.

1520frontrearsize = 2

front == null → false; rear stays the same.

20frontrearsize = 1

size-- → 1; return 15.

20frontrearsize = 1

isEmpty() → false (size 1).

20frontrearsize = 1

result = front.data = 20.

20rearfront = null · size = 1

front = front.next → null. But rear still points to the removed node!

20rearfront = null · size = 1

front == null → true: the queue is now empty.

20front = null · rear = null · size = 1

rear = null: without this line, rear would go on pointing to the removed node in an empty queue (an inconsistent state).

front = null · rear = null · size = 0

size-- → 0; return 20. Items left in the order 10, 15, 20 (FIFO), each in O(1).

public int dequeue() {
    if (isEmpty())
        throw new NoSuchElementException();
    int result = front.data;
    front = front.next;
    if (front == null)
        rear = null;
    size--;
    return result;
}

Linked Queues

Circular Array or Linked List?

Circular array

  • Contiguous in memory: cache-friendly and fast.
  • No extra reference per item.
  • Fixed capacity: when full, either overflow or copy into a larger array (O(n), amortized O(1)).

Linked list

  • No capacity limit; grows as needed.
  • Every insertion and removal is strictly O(1).
  • Each node costs an extra reference and an object allocation.
OperationCircular arrayLinked list
enqueueO(1)O(1)
dequeueO(1)O(1)
first / size / isEmptyO(1)O(1)
MemoryO(N) (the capacity)O(n) (the number of items)

Kinds of Queues

Kinds of Queues

KindInsertRemoveNote
Simple queuerearfrontFIFO
Input-restrictedrear onlyfront and rearrestricted deque
Output-restrictedfront and rearfront onlyrestricted deque
Circularrearfrontarray slots are reused
Double-ended (deque)front and rearfront and rearnot bound to FIFO
Priorityanywherehighest priorityordered by priority
Deque, short for double-ended queue, is pronounced “deck.” Don't confuse it with dequeue, the remove-from-queue operation.

Kinds of Queues

The Double-Ended Queue (Deque)

2726222120232425front (head)rear (tail)addremoveaddremove
  • A double-ended queue supports insertion and removal at both the front and the rear.
  • Not bound to FIFO: used only at the rear, a deque is a stack (LIFO); inserting at the rear and removing at the front makes it a queue.
OperationJava ArrayDequeTime
addFirst(e)offerFirst(e)O(1)
addLast(e)offerLast(e)O(1)
removeFirst()pollFirst()O(1)
removeLast()pollLast()O(1)
first() / last()peekFirst() / peekLast()O(1)

Kinds of Queues

Deque Operations, Step by Step

empty deque: head = tail = nullsize = 0

An empty deque. We implement it with a doubly linked list: every node has prev and next.

5nullnullheadtailaddLast(5)size = 1

First node: head = tail = node.

58nullnullheadtailaddLast(8)size = 2

Insert at the tail: node.prev = tail; tail.next = node; tail = node.

358nullnullheadtailaddFirst(3)size = 3

Insert at the head: node.next = head; head.prev = node; head = node.

3589nullnullheadtailaddLast(9)size = 4

Both ends are held by references, so every insertion is O(1).

358nullnullheadtailremoveLast()returned 9 · size = 3

Remove from the tail: tail = tail.prev; tail.next = null. The prev reference makes this O(1).

58nullnullheadtailremoveFirst()returned 3 · size = 2

Remove from the head: head = head.next; head.prev = null.

In a singly linked list, removing the tail is O(n), since we must find the node before it. The prev reference makes it O(1).

Kinds of Queues

Deque: Inserting at Either End

public void addFirst(E data) {
    Node<E> node = new Node<>(data);
    if (isEmpty()) {
        head = tail = node;
    } else {
        node.next = head;
        head.prev = node;
        head = node;
    }
    size++;
}
public void addLast(E data) {
    Node<E> node = new Node<>(data);
    if (isEmpty()) {
        head = tail = node;
    } else {
        node.prev = tail;
        tail.next = node;
        tail = node;
    }
    size++;
}
class Node<E> {
    E data;
    Node<E> prev, next;
    Node(E data) { this.data = data; }
}

The two methods mirror each other: head ↔ tail, prev ↔ next.

Kinds of Queues · Step through the code

Code Trace: Deque addFirst()

head = null · tail = null · size = 0

Empty deque. First addFirst(5), then addFirst(3): 3 will end up in front.

5nodehead = null · tail = null · size = 0

new Node<>(5): prev = next = null.

5nodehead = null · tail = null · size = 0

isEmpty() → true.

5head, node, tailsize = 0

head = tail = node: a single node forms both ends.

5head, node, tailsize = 1

size++ → 1.

53head, tailnodesize = 1

new Node<>(3): prev = next = null.

53head, tailnodesize = 1

isEmpty() → false.

53head, tailnodesize = 1

node.next = head: the new node points to the old head (5).

53head, tailnodesize = 1

head.prev = node: the back link; the list can now be walked in both directions.

53head, nodetailsize = 1

head = node: the head reference moves to the new node.

53head, nodetailsize = 2

size++ → 2. No traversal: O(1). addLast is its mirror image.

public void addFirst(E data) {
    Node<E> node = new Node<>(data);
    if (isEmpty()) {
        head = tail = node;
    } else {
        node.next = head;
        head.prev = node;
        head = node;
    }
    size++;
}

Kinds of Queues

Deque: Removing from Either End

public E removeFirst() {
    if (isEmpty()) return null;
    E data = head.data;
    head = head.next;
    if (head == null)
        tail = null;       // now empty
    else
        head.prev = null;
    size--;
    return data;
}
public E removeLast() {
    if (isEmpty()) return null;
    E data = tail.data;
    tail = tail.prev;
    if (tail == null)
        head = null;       // now empty
    else
        tail.next = null;
    size--;
    return data;
}
When the last item leaves, both ends must become null. Otherwise tail still points to the removed node once head is null: the node cannot be collected, and last() returns a wrong value. An empty deque returns null, as in Goodrich.

Kinds of Queues · Step through the code

Code Trace: Deque removeLast()

53headtailsize = 2

Deque: 3 ⇄ 5. Two calls to removeLast(): 5 comes out first, then 3, and the deque is left empty.

53headtailsize = 2

isEmpty() → false (size 2).

53headtailsize = 2

data = tail.data = 5.

53head, tailsize = 2

tail = tail.prev → node(3). Thanks to prev, there is no need to search for the second-to-last node.

53head, tailsize = 2

tail == null → false: the deque is not empty yet.

53head, tailsize = 2

tail.next = null: the link to node 5 is cut, and the node leaves the list.

3head, tailsize = 1

size-- → 1; return 5.

3head, tailsize = 1

isEmpty() → false (size 1).

3head, tailsize = 1

data = tail.data = 3.

3headtail = null · size = 1

tail = tail.prev → null (there was only one node). But head still points to it!

3headtail = null · size = 1

tail == null → true: the deque is now empty.

3head = null · tail = null · size = 1

head = null: both ends must be null; otherwise head would point to the removed node.

head = null · tail = null · size = 0

size-- → 0; return 3. O(1) in both cases.

public E removeLast() {
    if (isEmpty()) return null;
    E data = tail.data;
    tail = tail.prev;
    if (tail == null)
        head = null;       // now empty
    else
        tail.next = null;
    size--;
    return data;
}

Kinds of Queues · Step through the code

A Deque Application: Sliding Window Maximum

a01132-13-3455366d: index:valued (front)emptymaxes = []

k = 3. d holds indices whose values decrease from front to rear, so the maximum is always at the front.

a01132-13-3455366d: index:valued (front)0:1maxes = []

a[0] = 1: 0 goes in at the rear.

a01132-13-3455366d: index:valued (front)1:3maxes = []

a[1] = 3: drop 0:1 from the rear. 1 goes in at the rear.

a01132-13-3455366d: index:valued (front)1:32:-1maxes = [3]

a[2] = -1: 2 goes in at the rear. Max = 3.

a01132-13-3455366d: index:valued (front)1:32:-13:-3maxes = [3, 3]

a[3] = -3: 3 goes in at the rear. Max = 3.

a01132-13-3455366d: index:valued (front)4:5maxes = [3, 3, 5]

a[4] = 5: drop 1 from the front; drop 3:-3 and then 2:-1 from the rear. 4 goes in at the rear. Max = 5.

a01132-13-3455366d: index:valued (front)4:55:3maxes = [3, 3, 5, 5]

a[5] = 3: 5 goes in at the rear. Max = 5.

a01132-13-3455366d: index:valued (front)6:6maxes = [3, 3, 5, 5, 6]

a[6] = 6: drop 5:3 and then 4:5 from the rear. 6 goes in at the rear. Max = 6.

a01132-13-3455366d: index:valued (front)6:6maxes = [3, 3, 5, 5, 6]

Each index enters once and leaves at most once → O(n) (brute force takes O(n·k)).

int[] windowMax(int[] a, int k) {
  int[] maxes = new int[a.length - k + 1];
  Deque<Integer> d = new ArrayDeque<>(); // indices
  for (int i = 0; i < a.length; i++) {
    if (!d.isEmpty() && d.peekFirst() <= i - k)
      d.pollFirst();        // out of window
    while (!d.isEmpty() && a[d.peekLast()] <= a[i])
      d.pollLast();         // not a candidate
    d.offerLast(i);
    if (i >= k - 1) maxes[i - k + 1] = a[d.peekFirst()];
  }
  return maxes;
}

Applications

Application: A Round-Robin Scheduler

The operating system gives the CPU to processes in turn: the process at the front runs for at most q time units and, if unfinished, rejoins at the rear.

Ready queue (FIFO)P1 · 5P2 · 3P3 · 2CPUDone—Timeline (Gantt)012345678910t = 0 · quantum q = 2

Three processes in the ready queue, with 5, 3, and 2 units of work left. Time slice (quantum) q = 2.

Ready queue (FIFO)P2 · 3P3 · 2P1 · 3CPUP1Done—Timeline (Gantt)P1012345678910t = 2 · quantum q = 2

P1 leaves the front (dequeue), runs for 2 units, and rejoins at the rear with 3 left (enqueue).

Ready queue (FIFO)P3 · 2P1 · 3P2 · 1CPUP2Done—Timeline (Gantt)P1P2012345678910t = 4 · quantum q = 2

P2 runs for 2 units and goes back to the rear with 1 left.

Ready queue (FIFO)P1 · 3P2 · 1CPUP3DoneP3Timeline (Gantt)P1P2P3012345678910t = 6 · quantum q = 2

P3's 2 units fit in one slice → it finishes at t = 6 and does not rejoin.

Ready queue (FIFO)P2 · 1P1 · 1CPUP1DoneP3Timeline (Gantt)P1P2P3P1012345678910t = 8 · quantum q = 2

P1 runs for another 2 units; 1 is left.

Ready queue (FIFO)P1 · 1CPUP2DoneP3, P2Timeline (Gantt)P1P2P3P1P2012345678910t = 9 · quantum q = 2

P2 finishes its last unit (t = 9) without using the whole slice.

Ready queue (FIFO)emptyCPUP1DoneP3, P2, P1Timeline (Gantt)P1P2P3P1P2P1012345678910t = 10 · quantum q = 2

All done. Completion times: P3 = 6, P2 = 9, P1 = 10.

Simulator: Round-Robin Scheduler (adjustable quantum and processes).

Applications · Step through the code

Code Trace: roundRobin(q, 2)

ready queue (front → rear)P1:5P2:3P3:2CPUt = 0012345678910

roundRobin(q, 2): three processes with 5, 3, and 2 units left (Process: name, remaining). t = 0.

ready queue (front → rear)P2:3P3:2CPUP1:5t = 0012345678910

Not empty → p = q.poll() = P1 (5 left).

ready queue (front → rear)P2:3P3:2CPUP1:3t = 2P1012345678910

run = min(2, 5) = 2; P1 runs for 2 units → t = 2, 3 left.

ready queue (front → rear)P2:3P3:2P1:3CPUt = 2P1012345678910

remaining 3 > 0 → q.offer(p): P1 rejoins at the rear.

ready queue (front → rear)P3:2P1:3CPUP2:3t = 2P1012345678910

Not empty → p = q.poll() = P2 (3 left).

ready queue (front → rear)P3:2P1:3CPUP2:1t = 4P1P2012345678910

run = min(2, 3) = 2; P2 runs for 2 units → t = 4, 1 left.

ready queue (front → rear)P3:2P1:3P2:1CPUt = 4P1P2012345678910

remaining 1 > 0 → q.offer(p): P2 rejoins at the rear.

ready queue (front → rear)P1:3P2:1CPUP3:2t = 4P1P2012345678910

Not empty → p = q.poll() = P3 (2 left).

ready queue (front → rear)P1:3P2:1CPUP3:0t = 6P1P2P3012345678910

run = min(2, 2) = 2; P3 runs for 2 units → t = 6, 0 left.

ready queue (front → rear)P1:3P2:1CPUt = 6P1P2P3012345678910

remaining 0 → P3 is done (t = 6); it does not rejoin.

ready queue (front → rear)P2:1CPUP1:3t = 6P1P2P3012345678910

Not empty → p = q.poll() = P1 (3 left).

ready queue (front → rear)P2:1CPUP1:1t = 8P1P2P3P1012345678910

run = min(2, 3) = 2; P1 runs for 2 units → t = 8, 1 left.

ready queue (front → rear)P2:1P1:1CPUt = 8P1P2P3P1012345678910

remaining 1 > 0 → q.offer(p): P1 rejoins at the rear.

ready queue (front → rear)P1:1CPUP2:1t = 8P1P2P3P1012345678910

Not empty → p = q.poll() = P2 (1 left).

ready queue (front → rear)P1:1CPUP2:0t = 9P1P2P3P1P2012345678910

run = min(2, 1) = 1; P2 runs for 1 unit → t = 9, 0 left. The slice is not used up.

ready queue (front → rear)P1:1CPUt = 9P1P2P3P1P2012345678910

remaining 0 → P2 is done (t = 9); it does not rejoin.

ready queue (front → rear)emptyCPUP1:1t = 9P1P2P3P1P2012345678910

Not empty → p = q.poll() = P1 (1 left).

ready queue (front → rear)emptyCPUP1:0t = 10P1P2P3P1P2P1012345678910

run = min(2, 1) = 1; P1 runs for 1 unit → t = 10, 0 left. The slice is not used up.

ready queue (front → rear)emptyCPUt = 10P1P2P3P1P2P1012345678910

remaining 0 → P1 is done (t = 10); it does not rejoin.

ready queue (front → rear)emptyCPUt = 10P1P2P3P1P2P1012345678910

The queue is empty → the loop ends. Completion: P3 = 6, P2 = 9, P1 = 10. Each round is one poll plus at most one offer: O(1).

void roundRobin(Queue<Process> q, int quantum) {
  int t = 0;
  while (!q.isEmpty()) {
    Process p = q.poll();
    int run = Math.min(quantum, p.remaining);
    t += run;
    p.remaining -= run;
    if (p.remaining > 0) q.offer(p);
    else System.out.println(p.name + " t=" + t);
  }
}

Applications · Step through the code

Code Trace: The Josephus Problem (n = 5, k = 3)

ABCDEcirclequeue (front → rear)ABCDEeliminated—

Five people stand in a circle. Each round, the first k − 1 = 2 move to the rear and the 3rd is eliminated. A queue rotates the circle naturally.

ABCDEcirclequeue (front → rear)BCDEAA moved to the rear (poll + offer)eliminated—

i = 1: q.offer(q.poll()) → A moves to the rear.

ABCDEcirclequeue (front → rear)CDEABB moved to the rear (poll + offer)eliminated—

i = 2: q.offer(q.poll()) → B moves to the rear.

ABCDEcirclequeue (front → rear)DEABC eliminatedeliminatedC

C leaves the front and is eliminated. 4 left.

ABCDEcirclequeue (front → rear)EABDD moved to the rear (poll + offer)eliminatedC

i = 1: q.offer(q.poll()) → D moves to the rear.

ABCDEcirclequeue (front → rear)ABDEE moved to the rear (poll + offer)eliminatedC

i = 2: q.offer(q.poll()) → E moves to the rear.

ABCDEcirclequeue (front → rear)BDEA eliminatedeliminatedC A

A leaves the front and is eliminated. 3 left.

ABCDEcirclequeue (front → rear)DEBB moved to the rear (poll + offer)eliminatedC A

i = 1: q.offer(q.poll()) → B moves to the rear.

ABCDEcirclequeue (front → rear)EBDD moved to the rear (poll + offer)eliminatedC A

i = 2: q.offer(q.poll()) → D moves to the rear.

ABCDEcirclequeue (front → rear)BDE eliminatedeliminatedC A E

E leaves the front and is eliminated. 2 left.

ABCDEcirclequeue (front → rear)DBB moved to the rear (poll + offer)eliminatedC A E

i = 1: q.offer(q.poll()) → B moves to the rear.

ABCDEcirclequeue (front → rear)BDD moved to the rear (poll + offer)eliminatedC A E

i = 2: q.offer(q.poll()) → D moves to the rear.

ABCDEcirclequeue (front → rear)DB eliminatedeliminatedC A E B

B leaves the front and is eliminated. 1 left.

ABCDEcirclequeue (front → rear)DeliminatedC A E B

One person is left: the winner is D. n people, k steps: O(n·k) queue operations.

String josephus(Queue<String> q, int k) {
  while (q.size() > 1) {
    for (int i = 1; i < k; i++)
      q.offer(q.poll());      // front to rear
    String out = q.poll();
    System.out.println(out + " eliminated");
  }
  return q.poll();
}

Applications

Example: Binary Numbers from 1 to n

Use a queue to generate the binary representations of the numbers from 1 to n.

Example 1n = 3 → {"1", "10", "11"}
Example 2n = 5 → {"1", "10", "11", "100", "101"}
01010111011100101110111
Idea: x has two successors, x + "0" (2x) and x + "1" (2x + 1). Dequeue a number, output it, and enqueue its successors: the numbers come out level by level, in increasing order (breadth-first).

Applications · Step through the code

Code Trace: generateBinary(4)

q (front →)resultempty0123

result: an array with 4 empty slots.

q (front →)resultempty0123

An empty queue is created (a LinkedList behind the Queue interface).

q (front →)result10123

The root “1” is enqueued.

q (front →)result10123

i = 0 < 4 → loop body.

q (front →)resultempty01123

result[0] = q.poll() = “1”: the smallest number, at the front of the queue.

q (front →)result101101123

“10” and “11” are enqueued: the successors of “1”, to come out in turn.

q (front →)result101101123

i = 1 < 4 → loop body.

q (front →)result110111023

result[1] = q.poll() = “10”: the smallest number, at the front of the queue.

q (front →)result111001010111023

“100” and “101” are enqueued: the successors of “10”, to come out in turn.

q (front →)result111001010111023

i = 2 < 4 → loop body.

q (front →)result100101011102113

result[2] = q.poll() = “11”: the smallest number, at the front of the queue.

q (front →)result100101110111011102113

“110” and “111” are enqueued: the successors of “11”, to come out in turn.

q (front →)result100101110111011102113

i = 3 < 4 → loop body.

q (front →)result101110111011102113100

result[3] = q.poll() = “100”: the smallest number, at the front of the queue.

q (front →)result10111011110001001011102113100

“1000” and “1001” are enqueued: the successors of “100”, to come out in turn.

q (front →)result10111011110001001011102113100

i = 4 < 4 is false → the loop ends.

q (front →)result10111011110001001011102113100

return {“1”, “10”, “11”, “100”}. n polls + 2n offers → O(n) queue operations (the strings have length O(log n), so the total character work is O(n log n)).

String[] generateBinary(int n) {
    String[] result = new String[n];
    Queue<String> q = new LinkedList<>();
    q.offer("1");
    for (int i = 0; i < n; i++) {
        result[i] = q.poll();
        q.offer(result[i] + "0");
        q.offer(result[i] + "1");
    }
    return result;
}

Applications · Step through the code

Code Trace: A Queue from Two Stacks

emptyinboxemptyoutboxenqueue(1)enqueue(2)enqueue(3)dequeue()enqueue(4)dequeue()dequeue()dequeue()output order: —

inbox takes insertions and outbox serves removals (stack tops are drawn at the top).

1inboxemptyoutboxenqueue(1)enqueue(2)enqueue(3)dequeue()enqueue(4)dequeue()dequeue()dequeue()output order: —

enqueue(1): inbox.push(1) → O(1).

12inboxemptyoutboxenqueue(1)enqueue(2)enqueue(3)dequeue()enqueue(4)dequeue()dequeue()dequeue()output order: —

enqueue(2): inbox.push(2) → O(1).

123inboxemptyoutboxenqueue(1)enqueue(2)enqueue(3)dequeue()enqueue(4)dequeue()dequeue()dequeue()output order: —

enqueue(3): inbox.push(3) → O(1).

123inboxemptyoutboxenqueue(1)enqueue(2)enqueue(3)dequeue()enqueue(4)dequeue()dequeue()dequeue()output order: —

dequeue: outbox is empty → move everything from inbox to outbox.

12inbox3outboxenqueue(1)enqueue(2)enqueue(3)dequeue()enqueue(4)dequeue()dequeue()dequeue()output order: —

3: pop from inbox, push onto outbox. The transfer reverses the order; the oldest item ends up on top.

1inbox32outboxenqueue(1)enqueue(2)enqueue(3)dequeue()enqueue(4)dequeue()dequeue()dequeue()output order: —

2: pop from inbox, push onto outbox. The transfer reverses the order; the oldest item ends up on top.

emptyinbox321outboxenqueue(1)enqueue(2)enqueue(3)dequeue()enqueue(4)dequeue()dequeue()dequeue()output order: —

1: pop from inbox, push onto outbox. The transfer reverses the order; the oldest item ends up on top.

emptyinbox32outboxenqueue(1)enqueue(2)enqueue(3)dequeue()enqueue(4)dequeue()dequeue()dequeue()output order: 1

outbox.pop() → 1 (FIFO: oldest first).

4inbox32outboxenqueue(1)enqueue(2)enqueue(3)dequeue()enqueue(4)dequeue()dequeue()dequeue()output order: 1

enqueue(4): inbox.push(4) → O(1).

4inbox32outboxenqueue(1)enqueue(2)enqueue(3)dequeue()enqueue(4)dequeue()dequeue()dequeue()output order: 1

dequeue: outbox is not empty → no transfer.

4inbox3outboxenqueue(1)enqueue(2)enqueue(3)dequeue()enqueue(4)dequeue()dequeue()dequeue()output order: 1 2

outbox.pop() → 2 (FIFO: oldest first).

4inbox3outboxenqueue(1)enqueue(2)enqueue(3)dequeue()enqueue(4)dequeue()dequeue()dequeue()output order: 1 2

dequeue: outbox is not empty → no transfer.

4inboxemptyoutboxenqueue(1)enqueue(2)enqueue(3)dequeue()enqueue(4)dequeue()dequeue()dequeue()output order: 1 2 3

outbox.pop() → 3 (FIFO: oldest first).

4inboxemptyoutboxenqueue(1)enqueue(2)enqueue(3)dequeue()enqueue(4)dequeue()dequeue()dequeue()output order: 1 2 3

dequeue: outbox is empty → move everything from inbox to outbox.

emptyinbox4outboxenqueue(1)enqueue(2)enqueue(3)dequeue()enqueue(4)dequeue()dequeue()dequeue()output order: 1 2 3

4: pop from inbox, push onto outbox. The transfer reverses the order; the oldest item ends up on top.

emptyinboxemptyoutboxenqueue(1)enqueue(2)enqueue(3)dequeue()enqueue(4)dequeue()dequeue()dequeue()output order: 1 2 3 4

outbox.pop() → 4. Output 1, 2, 3, 4 matches the input order. Each item is transferred at most once → dequeue is amortized O(1) (a single call is O(n) in the worst case).

Deque<Integer> inbox = new ArrayDeque<>();
Deque<Integer> outbox = new ArrayDeque<>();
void enqueue(int x) {
  inbox.push(x);
}
int dequeue() {
  if (outbox.isEmpty())
    while (!inbox.isEmpty())
      outbox.push(inbox.pop());
  return outbox.pop();
}

Summary

Common Mistakes

Forgetting rear on the last removalWhen a linked queue loses its last node, front = null; without rear = null, rear points to a removed node.
Mixing up full and emptyWith only front and rear, front == rear both when empty and when full; add a size counter or leave one slot empty.
Arrays.copyOf when growingIt scrambles a circular queue that has wrapped around; copy the items in (front + k) % N order.
add / offer, remove / polladd, remove, and element throw exceptions; offer, poll, and peek return false / null.
PriorityQueue is not FIFOQueue<Integer> q = new PriorityQueue<>() hands out items by priority, not by arrival order.
ArrayDeque and nullArrayDeque rejects null items (NullPointerException); a null would be confused with the “empty” result.

Summary

Summary: Costs of Queue Implementations

ImplementationInsertRemoveFrontNote
Simple array (with shifting)O(1)O(n)O(1)shifts on every removal
Circular arrayO(1)O(1)O(1)fixed capacity, % N
Linked listO(1)O(1)O(1)front and rear references
Deque (doubly linked list)O(1) both endsO(1) both endsO(1)prev reference
Queue from two stacksO(1)O(1) amortizedO(1) amortizedeach item is transferred once
FIFOInsert at the rear, remove from the front.
Circular arrayTelling empty from full: a size counter or one empty slot.
Edge casesRemoving from an empty queue (underflow); rear = null when the last item leaves.

Summary

Interactive Simulators

Queue Simulatorenqueue, dequeue, front, rear, and search, step by step.
Deque SimulatorThe effect of inserting and removing at both ends.
Circular Queue Simulatorfront, size, wraparound with mod, and growing a full array, in array and ring views.
Round-Robin SchedulerA FIFO ready queue, adjustable quantum, and process metrics.

Summary

Sample Problems

  1. Starting from an empty queue: enqueue(4), enqueue(7), dequeue(), enqueue(1), enqueue(9), dequeue(). What is first(), and what is left in the queue?
  2. N = 6, front = 4, size = 3: which indices hold the items, and where does the next enqueue write?
  3. With the “one empty slot” method and N = 10, how many items fit at most? What is the full condition?
  4. What happens if if (front == null) rear = null; is removed from dequeue?
  5. How do you build a queue from two stacks? What is the amortized cost of dequeue?
  6. How can a deque check whether a word is a palindrome?
Show answers
  1. first() = 1; the queue (front to rear): 1, 9.
  2. Indices 4, 5, 0. Next write: (4 + 3) % 6 = 1.
  3. 9 items; full when (rear + 1) % 10 == front.
  4. In an empty queue, rear still points to the old node: rear() returns a wrong value, and the node cannot be collected (an inconsistent state).
  5. Enqueue: push onto inbox. Dequeue: if outbox is empty, move inbox into outbox, then pop from outbox. Each item is transferred at most once → amortized O(1).
  6. Put the letters in a deque; repeatedly remove one from each end and compare. O(n).

Summary

Exercises

  1. Reverse a queue using only queue operations and one stack.
  2. Build a stack from one queue: how do you write push, and at what cost?
  3. Josephus: people numbered 1..7, k = 3. In what order are they eliminated, and who wins?
  4. A circular queue with N = 5, front = 3, size = 5 is grown. In what order do the items appear in the new array?
  5. Reverse the first k items of a queue without changing the order of the rest.
  6. What changes in the deque algorithm for the sliding window minimum?
Hints / answers
  1. Dequeue everything and push it onto the stack, then pop and enqueue → O(n).
  2. push(x): enqueue(x), then move the size − 1 items in front of x to the rear with dequeue+enqueue → push O(n), pop O(1).
  3. Eliminated in the order 3, 6, 2, 7, 5, 1; the winner is 4.
  4. data[3], data[4], data[0], data[1], data[2] → temp[0..4]; front = 0.
  5. Push the first k onto a stack and enqueue them back, then rotate the remaining n − k items to the rear.
  6. The comparison flips: indices with a[d.peekLast()] >= a[i] are dropped.

Summary

Further Reading

  • Goodrich, Tamassia, and Goldwasser, Data Structures and Algorithms in Java, 6th ed., 2014.
  • Lafore, Data Structures and Algorithms in Java, 2nd ed., 2002.
  • Oracle, Java SE 17 API documentation for Queue, Deque, ArrayDeque, and PriorityQueue.

The End

Chapter 8: Queues and Deques
Next deck: Chapter 9: Trees · Study questions · Simulators: Queue, Deque, Circular Queue, Round-Robin Scheduler · Glossary
1 / 1 Sercan KÜLCÜ. All rights reserved.