Priority Queues

CS205 Data Structures

Urgent ──┐ Medium ───┤ ┌─────────────┐ Low ───┼──►│ Priority PQ │──► Highest priority out first Medium ───┤ └─────────────┘ Urgent ───┘

Use arrow keys or buttons to navigate · Press Home/End to jump

1 / 18

What is a Priority Queue?

A collection where each element has a priority. The element with the highest priority (lowest key) is served first -- not the one that arrived first.

Regular Queue (FIFO)

enqueue ──►┌───┬───┬───┬───┐──► dequeue │ D │ C │ B │ A │ └───┴───┴───┴───┘ First in ─────────────────────► First out

Whoever arrives first, leaves first.

Priority Queue

insert ──► ┌───┬───┬───┬───┐──► removeMin │ 7 │ 2 │ 9 │ 4 │ └───┴───┴───┴───┘ Any order ──────────────────► Lowest key out

Lowest key (highest priority) leaves first, regardless of arrival.

Analogy: Hospital ER Triage

Waiting Room: ┌────────────────────────────────┐ │ Patient A (sprained ankle) 3 │ │ Patient B (chest pain) 1 │ ◄── seen FIRST │ Patient C (mild fever) 4 │ │ Patient D (broken arm) 2 │ └────────────────────────────────┘ Priority: 1 = critical ... 5 = minor

Patients are not seen in arrival order. The most critical case (lowest priority number) is treated first.

Key Idea

A priority queue is an ADT that supports inserting elements with keys and removing the element whose key is minimal (or maximal, depending on convention).

2 / 18

The Priority Queue ADT

A priority queue stores a collection of entries, each a (key, value) pair.

Core Operations

MethodDescription
insert(k, v)Insert entry with key k and value v
removeMin()Remove & return entry with smallest key
min()Return (but don't remove) entry with smallest key
size()Return number of entries
isEmpty()Is the PQ empty?

Trace Example

Operation PQ contents Return ───────────────── ──────────────── ────── insert(5, "A") {(5,A)} entry insert(9, "C") {(5,A),(9,C)} entry insert(3, "B") {(5,A),(9,C), entry (3,B)} min() {(5,A),(9,C), (3,B) (3,B)} removeMin() {(5,A),(9,C)} (3,B) removeMin() {(9,C)} (5,A) size() {(9,C)} 1 removeMin() {} (9,C) isEmpty() {} true

Warning

removeMin() and min() throw an error (or return null) if called on an empty priority queue. Always check isEmpty() first.

3 / 18

Keys and Comparators

Keys define priority. But how do we compare them?

Total Order Relations

A comparison rule must satisfy:

  • Reflexive: k ≤ k
  • Antisymmetric: if k1 ≤ k2 and k2 ≤ k1, then k1 = k2
  • Transitive: if k1 ≤ k2 and k2 ≤ k3, then k1 ≤ k3

Key Idea

Any two keys must be comparable. This is what makes it a total order (as opposed to a partial order).

The Comparator Pattern

interface Comparator<K> { int compare(K a, K b); // returns: // negative if a < b // zero if a == b // positive if a > b }

Natural vs Custom Ordering

ApproachExample
NaturalIntegers: 1 < 2 < 3 ...
CustomStrings by length, Points by x-coord

Analogy

A comparator is like a judge at a competition -- you can swap in a different judge to rank contestants by a different criterion.

4 / 18

Implementation 1: Unsorted List

Store entries in an unsorted linked list or array. Insertion is fast, but finding the minimum requires scanning.

How It Works

insert(3,"X") insert(7,"Y") insert(1,"Z") insert(5,"W") List (unsorted): ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ │(3,"X")│──►│(7,"Y")│──►│(1,"Z")│──►│(5,"W")│ └───────┘ └───────┘ └───────┘ └───────┘ removeMin() — must scan ALL entries: ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ │(3,"X")│──►│(7,"Y")│──►│(1,"Z")│──►│(5,"W")│ └───────┘ └───────┘ └───┬───┘ └───────┘ check check ▲ MIN! check │ remove & return (1,"Z")

Complexity

OperationTime
insertO(1)
removeMinO(n)
minO(n)
size, isEmptyO(1)

Key Idea

Insert is O(1) because we just append. But removeMin is O(n) because we must scan every entry to find the smallest key.

Warning

Good for insert-heavy workloads. Terrible if you remove frequently.

5 / 18

Implementation 2: Sorted List

Keep entries sorted by key. The minimum is always at the front, but insertion requires finding the right position.

How It Works

insert(3,"X") insert(7,"Y") insert(1,"Z") insert(5,"W") List (sorted by key): ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ │(1,"Z")│──►│(3,"X")│──►│(5,"W")│──►│(7,"Y")│ └───────┘ └───────┘ └───────┘ └───────┘ ▲ MIN! removeMin() — just remove the front! ┌───────┐ ┌───────┐ ┌───────┐ │(3,"X")│──►│(5,"W")│──►│(7,"Y")│ └───────┘ └───────┘ └───────┘ ▲ new MIN insert(4,"Q") — walk to find position: ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ │(3,"X")│──► ● │(4,"Q")│──►│(5,"W")│──►│(7,"Y")│ └───────┘ insert └───────┘ └───────┘ └───────┘

Complexity

OperationTime
insertO(n)
removeMinO(1)
minO(1)
size, isEmptyO(1)

Key Idea

removeMin is O(1) because the smallest key is always at the front. But insert is O(n) because we walk the list to find the sorted position.

Warning

Good for remove-heavy workloads. Terrible if you insert frequently.

6 / 18

Comparison: Unsorted vs Sorted List

Neither list-based approach gives us the best of both worlds.

Operation Unsorted List Sorted List
insert(k, v) O(1) O(n)
removeMin() O(n) O(1)
min() O(n) O(1)
Time │ O(n) │ ████ ████ │ ████ ████ │ ████ ████ O(1) │ ████ ████ │ ████ ████ └────────────────────────────── insert removeMin insert removeMin ◄─ Unsorted ─► ◄── Sorted ──►

Analogy

Unsorted list = throwing clothes into a pile. Fast to add, slow to find what you need.

Sorted list = keeping a perfectly organized closet. Slow to put away, fast to grab the right item.

Key Idea

Can we get O(log n) for both insert and removeMin? Yes -- with a heap! (Coming in the next lecture.)

7 / 18

Selection Sort Using a PQ

Use an unsorted list PQ to sort a sequence.

Algorithm

  1. Insert all n elements into the PQ — each insert is O(1)
  2. Call removeMin() n times — each is O(n), O(n-1), ... O(1)
  3. Elements come out in sorted order
Input: [7, 4, 8, 2, 5] Phase 1 — Insert all (unsorted list): PQ: {7, 4, 8, 2, 5} total: 5 x O(1) = O(n) Phase 2 — removeMin repeatedly: removeMin → 2 PQ: {7, 4, 8, 5} scan 5 items removeMin → 4 PQ: {7, 8, 5} scan 4 items removeMin → 5 PQ: {7, 8} scan 3 items removeMin → 7 PQ: {8} scan 2 items removeMin → 8 PQ: {} scan 1 item Output: [2, 4, 5, 7, 8] ✓ sorted!

Complexity Analysis

Phase 1: n inserts x O(1) = O(n) Phase 2: n + (n-1) + ... + 2 + 1 = n(n+1)/2 = O(n^2) Total: O(n) + O(n^2) = O(n^2)

Key Idea

This is exactly Selection Sort! Each removeMin "selects" the minimum from the remaining unsorted elements.

Warning

O(n^2) in all cases -- no best-case improvement. The scan is always required.

8 / 18

Insertion Sort Using a PQ

Use a sorted list PQ to sort a sequence.

Algorithm

  1. Insert all n elements into the PQ — each insert walks the sorted list
  2. Call removeMin() n times — each is O(1)
  3. Elements come out in sorted order
Input: [7, 4, 8, 2, 5] Phase 1 — Insert into sorted list: insert 7 → PQ: [7] walk 0 insert 4 → PQ: [4, 7] walk 1 insert 8 → PQ: [4, 7, 8] walk 2 insert 2 → PQ: [2, 4, 7, 8] walk 3 (front) insert 5 → PQ: [2, 4, 5, 7, 8] walk 2 Phase 2 — removeMin repeatedly: removeMin → 2 [4, 5, 7, 8] O(1) removeMin → 4 [5, 7, 8] O(1) removeMin → 5 [7, 8] O(1) removeMin → 7 [8] O(1) removeMin → 8 [] O(1) Output: [2, 4, 5, 7, 8] ✓ sorted!

Complexity Analysis

Phase 1: 1 + 2 + ... + (n-1) + n = n(n+1)/2 = O(n^2) Phase 2: n removes x O(1) = O(n) Total: O(n^2) + O(n) = O(n^2)

Key Idea

This is exactly Insertion Sort! Each insert places the element into its correct sorted position.

Analogy

Like sorting a hand of playing cards: you pick up each card and slide it into the right spot among the cards you're already holding.

9 / 18

Can We Do Better?

Both PQ-based sorts are O(n^2). Is there a middle ground?

insert removeMin PQ-Sort ────────────── ───────── ──────────── ────────── Unsorted List O(1) O(n) O(n^2) ← Selection Sort Sorted List O(n) O(1) O(n^2) ← Insertion Sort ╔═════════════════════════════════════════╗ ║ What if BOTH were O(log n)? ║ ║ ║ ║ insert: O(log n) removeMin: O(log n) ║ ║ PQ-Sort: O(n log n) ║ ║ ║ ║ >>> THE HEAP <<< ║ ╚═════════════════════════════════════════╝

Key Idea

A binary heap achieves O(log n) for both insert and removeMin by using a complete binary tree with the heap-order property.

The Heap: a "balanced" approach 2 ◄── min at root / \ 4 5 / \ \ 7 9 8 insert: bubble UP O(log n) removeMin: trickle DOWN O(log n) PQ-Sort: n x O(log n) = O(n log n) = Heap Sort!
10 / 18

The Entry / Key-Value Pattern

Priority queues store entries, not bare keys. Each entry is a (key, value) pair.

Why Separate Key from Value?

  • The key determines priority (how entries are ordered)
  • The value is the actual data you care about
  • The same value might need different priorities in different contexts
interface Entry<K, V> { K getKey(); // the priority V getValue(); // the payload }

Analogy

A boarding pass at an airport: the key is your boarding group number, the value is you (the passenger). The airline decides your priority, not your name.

Examples

ContextKeyValue
ER TriageSeverityPatient
Print QueuePriorityPrint Job
Event SimTimeEvent
Dijkstra'sDistanceVertex
HuffmanFrequencyTree Node
Entry examples: (1, "chest pain patient") ← treated first (3, "sprained ankle patient") ← treated later (5, "mild headache patient") ← treated last Key = severity Value = patient info

Key Idea

Entries decouple "what you store" from "how it's prioritized." This makes the PQ reusable across many domains.

11 / 18

Adaptable Priority Queue

Sometimes you need to change a key or remove an arbitrary entry -- not just the minimum.

Additional Operations

MethodDescription
remove(e)Remove entry e from PQ
replaceKey(e, k)Change key of entry e to k
replaceValue(e, v)Change value of entry e to v

Location-Aware Entries

Standard entry: ┌─────────────┐ │ key │ value │ knows nothing about └─────────────┘ where it is in the PQ Location-aware entry: ┌──────────────────────┐ │ key │ value │ locator│──► points to its └──────────────────────┘ position in PQ

The locator lets us jump directly to an entry in O(1), avoiding an O(n) search.

Why Do We Need This?

Dijkstra's algorithm scenario: PQ: { (10, A), (∞, B), (∞, C), (5, D) } We discover a shorter path to B (cost 7): replaceKey(entryB, 7) PQ: { (10, A), (7, B), (∞, C), (5, D) } ▲ key changed!

Warning

Without location-aware entries, replaceKey would require O(n) to find the entry first. With a locator, it's O(1) to find + O(log n) to re-heapify = O(log n) total.

Key Idea

An adaptable PQ extends the standard PQ by letting you modify entries already in the queue. This is essential for graph algorithms like Dijkstra's and Prim's.

12 / 18

Application: Job Scheduling

Operating systems use priority queues to decide which process runs next.

OS Process Scheduler

Running processes: ┌──────────────────────────────────────┐ │ Process Priority State │ │ ─────────── ────────── ────────── │ │ System IRQ 1 ready │ │ Video call 2 ready │ │ Compiler 5 ready │ │ Text editor 5 ready │ │ Backup job 10 ready │ │ Screen saver 15 ready │ └──────────────────────────────────────┘ PQ.removeMin() → System IRQ (priority 1) → runs on CPU PQ.removeMin() → Video call (priority 2) → runs on CPU New process arrives: PQ.insert(3, "Web browser") → inserted into PQ

How the PQ Helps

┌──────────┐ │ CPU │ ◄── always runs the └────┬─────┘ highest-priority process │ removeMin() │ ┌────────▼────────┐ │ Priority Queue │ ◄── processes waiting │ ┌──┬──┬──┬──┐ │ │ │1 │2 │5 │10│ │ │ └──┴──┴──┴──┘ │ └────────▲────────┘ │ insert() │ ┌────────┴────────┐ │ New processes │ │ arriving │ └─────────────────┘

Analogy

Like a hospital with one doctor: the most critical patient always gets treated next, but new patients can arrive at any time and get triaged into the queue.

Key Idea

The PQ efficiently finds the next process to schedule. With a heap, this costs O(log n) per operation -- critical when the OS handles thousands of processes.

13 / 18

Application: Event-Driven Simulation

Simulate a system by processing events in chronological order, even if they were generated out of order.

How It Works

Timeline of events: t=0 t=2 t=5 t=7 t=12 t=15 │ │ │ │ │ │ ▼ ▼ ▼ ▼ ▼ ▼ Start Arrive Serve Arrive Serve End Priority Queue (key = event time): ┌──────────────────────────────────────┐ │ (0,Start) (2,Arrive) (5,Serve) ... │ └──────────────────────────────────────┘ Loop: while PQ is not empty: event = PQ.removeMin() ← earliest event process(event) // processing may INSERT new future events

Key Idea

Events are entries with time as the key. Processing an event often creates new events (inserted with future timestamps). The PQ always gives us the chronologically next event.

Example: Bank Simulation

Step 1: removeMin → (0, "Bank opens") → insert(2, "Customer A arrives") → insert(5, "Customer B arrives") Step 2: removeMin → (2, "Customer A arrives") → insert(7, "Customer A done") Step 3: removeMin → (5, "Customer B arrives") → insert(12, "Customer B done") Step 4: removeMin → (7, "Customer A done") → "Customer A leaves" Step 5: removeMin → (12, "Customer B done") → "Customer B leaves"

Analogy

Like a director with a schedule of scenes to film. Each scene might add new scenes to the schedule. The PQ always picks the scene that happens earliest in the story timeline.

14 / 18

Application: Dijkstra's Algorithm Preview

The priority queue is the engine behind the famous shortest-path algorithm.

The Idea

Graph with weighted edges: 2 3 A ────── B ────── D │ │ │ │ 4 │ 1 │ 2 │ │ │ C ────── E ────── F 5 1 Find shortest path from A to all others.
Algorithm sketch: PQ = { (0, A) } ← start: dist to A is 0 while PQ not empty: (d, u) = PQ.removeMin() ← closest vertex for each neighbor v of u: if d + weight(u,v) < dist[v]: dist[v] = d + weight(u,v) PQ.insert(dist[v], v) // or replaceKey if using adaptable PQ

PQ Trace (from A)

Step removeMin PQ after updates ──── ─────────── ────────────────────── 1 (0, A) {(2,B), (4,C)} 2 (2, B) {(3,E), (4,C), (5,D)} 3 (3, E) {(4,C), (4,F), (5,D)} 4 (4, C) {(4,F), (5,D)} 5 (4, F) {(5,D)} → {(5,D)} 6 (5, D) {} Shortest distances from A: A=0 B=2 C=4 D=5 E=3 F=4

Key Idea

Dijkstra's "greedily" picks the unvisited vertex with the smallest known distance using removeMin(). With a heap-based PQ, the algorithm runs in O((V + E) log V).

Prerequisite You'll study this in detail in the Graph Algorithms unit.

15 / 18

Application: Huffman Coding Preview

Build optimal prefix-free codes for data compression using a priority queue.

The Idea

Frequent characters get short codes; rare characters get long codes.

Character frequencies: a: 45 b: 13 c: 12 d: 16 e: 9 f: 5 Algorithm: 1. Insert each character as a leaf node into PQ, keyed by frequency. 2. While PQ has more than one entry: a. T1 = PQ.removeMin() b. T2 = PQ.removeMin() c. Create new node with T1, T2 as children key = T1.freq + T2.freq d. PQ.insert(new node) 3. Last remaining entry is the Huffman tree root.

Building the Tree

PQ: [(5,f) (9,e) (12,c) (13,b) (16,d) (45,a)] removeMin → f(5), e(9) → merge: (14) PQ: [(12,c) (13,b) (14,fe) (16,d) (45,a)] removeMin → c(12), b(13) → merge: (25) PQ: [(14,fe) (16,d) (25,cb) (45,a)] removeMin → fe(14), d(16) → merge: (30) PQ: [(25,cb) (30,fed) (45,a)] removeMin → cb(25), fed(30) → merge: (55) PQ: [(45,a) (55,cbfed)] removeMin → a(45), cbfed(55) → merge: (100) PQ: [(100, root)] → DONE!

Key Idea

The PQ always merges the two least frequent nodes. This greedy strategy produces an optimal prefix code. Two removeMin + one insert per step.

16 / 18

Implementation Comparison

How do all PQ implementations stack up?

Operation Unsorted List Sorted List Binary Heap BST (balanced)
insert O(1) O(n) O(log n) O(log n)
removeMin O(n) O(1) O(log n) O(log n)
min O(n) O(1) O(1) O(log n)
PQ-Sort O(n^2) O(n^2) O(n log n) O(n log n)
Space O(n) O(n) O(n) O(n)

Key Idea

The binary heap is the sweet spot for priority queues: O(log n) insert and removeMin, O(1) min, simple array-based storage, and it gives us Heap Sort at O(n log n).

Warning

A balanced BST (like AVL or Red-Black) also achieves O(log n) for everything, but has higher constant factors and more complex implementation. Heaps are preferred when you only need PQ operations.

Choose your implementation: ┌──────────────┬──────────────────────────────────────────────────┐ │ Unsorted List│ Few removes, many inserts, small n │ │ Sorted List │ Few inserts, many removes, small n │ │ Binary Heap │ General purpose PQ — BEST default choice │ ◄── use this │ Balanced BST │ Need ordered iteration or range queries too │ └──────────────┴──────────────────────────────────────────────────┘
17 / 18

Summary & Cheat Sheet

Core Concepts

  • Priority Queue = collection where the element with the smallest (or largest) key is removed first
  • Entry = (key, value) pair; key determines priority
  • Comparator = defines the total order on keys
  • Adaptable PQ = supports remove(e) and replaceKey(e, k)

Sorting Connection

PQ Implementation → Sorting Algorithm ────────────────── ────────────────── Unsorted List → Selection Sort O(n^2) Sorted List → Insertion Sort O(n^2) Binary Heap → Heap Sort O(n log n)

Applications

  • OS Job / Process Scheduling
  • Event-Driven Simulation
  • Dijkstra's Shortest Path
  • Huffman Coding (compression)
  • Prim's Minimum Spanning Tree
  • A* Search (AI pathfinding)

Complexity Cheat Sheet

UnsortedSortedHeap
insertO(1)O(n)O(log n)
removeMinO(n)O(1)O(log n)
minO(n)O(1)O(1)

Key Takeaway

List-based PQs are simple but O(n) for at least one core operation. The binary heap (next lecture!) achieves O(log n) for both insert and removeMin -- the gold standard for priority queues.

┌─────────────────────────────────────────┐ │ WHAT TO REMEMBER │ │ │ │ 1. PQ ≠ Queue (not FIFO!) │ │ 2. Keys define priority │ │ 3. Entries = (key, value) pairs │ │ 4. Unsorted list → fast insert │ │ 5. Sorted list → fast removeMin │ │ 6. Heap → fast BOTH! │ │ 7. PQ-Sort with heap = O(n log n) │ └─────────────────────────────────────────┘

Next up: Binary Heaps -- the data structure that makes PQs efficient.

18 / 18