Skip to main content
Artificial Computational Intelligence

Heaps, Priority Queues, Dictionaries, and Hash Tables

Published: 2026-08-09
Level: postgraduate
Audience: Postgraduate students learning data structures and algorithm analysis

Prerequisite Knowledge

This lecture builds on the following concepts from earlier lectures. If any feel unfamiliar, review the linked notes before proceeding.

Previously Covered in This Subject

  • Heaps: definition and properties — covered in Lecture 5 (Trees and Heaps)
  • Heap insertion and upheap — covered in Lecture 5 (Trees and Heaps)
  • Heap removal and downheap — covered in Lecture 5 (Trees and Heaps)
  • Level numbering and the vector representation of complete binary trees — covered in Lecture 5 (Trees and Heaps)
  • The vector ADT and rank-based storage — covered in Lecture 4 (Abstract Data Types: Stacks, Queues, Lists, and Vectors)
  • The queue ADT — covered in Lecture 4 (Abstract Data Types: Stacks, Queues, Lists, and Vectors)
  • Big-O notation and order of growth — covered in Lecture 2 (Algorithm Analysis: From Basic Operations to Asymptotic Notation)

Heaps, Priority Queues, Dictionaries, and Hash Tables

6.1 Heap Operations Recap

6.1.1 Insertion and the Bubble-Up Step

This session opens with a quick recap of last session: we ended with the heap data structure — a complete binary tree whose nodes are stored level by level in a vector, with the heap property holding at every node. Two things made the heap usable: its structural property (the tree stays complete, so the vector has no holes) and its relational property (a parent is ordered against its children — larger than them in a max heap, smaller in a min heap). We also saw how to insert an element into a heap and how to remove one from a heap. Both operations rely on one fixed procedure: heapification, which restores the heap property after the heap changes.

To insert a new element, push it into the first available leaf position of the heap, then bubble it upward until the heap property holds again. The first available leaf is simply the next free slot of the underlying vector — because the tree is complete, that slot is unique and easy to reach. So the placement step is a constant-time operation; the bubble-up does the real work. The new element keeps exchanging places with its parent while the heap property is violated, climbing one level per exchange.

This bubble-up cost is bounded by the height of the tree: in the worst case the new element is the smallest (in a min-heap) and must climb from a leaf all the way to the root. The professor's point during the recap was that these answers should come instantly — "there is nothing to think about if you have understood" — because the procedure and its cost are the same every time.

6.1.2 Removal and the Bubble-Down Step

Removal exchanges the root with the last leaf of the heap, removes the last element, then heapifies the remaining tree to restore the heap property. Note the discipline of this operation: we always remove the root element — that is the only element we remove from a heap; we never remove an arbitrary element. The root is the only node whose identity is guaranteed by the heap property (the maximum in a max heap, the minimum in a min heap), so it is the only node we can remove without first searching.

The steps in order: exchange the root with the last element, delete that last element, and bubble the new root downward (downheap). The old root now sits in the sorted-out portion of the story — in practice it is simply gone from the heap. The new root, which came up from the bottom, is probably too small (for a max heap), so it sinks, each time swapping with its larger child, until the heap property holds again. Like the bubble-up, this costs the height of the tree: the worst case is a value that must sink from the root all the way to a leaf.

6.1.3 Visual Intuition — The Two Bubbles

Picture the heap as a pyramid drawn inside a flat vector. The bubble-up is a vertical path that starts at a new leaf and rises: the path follows parent links, and at each level the new value swaps with its parent if needed. The bubble-down is the mirror image: a path that starts at the root and sinks, swapping with the larger child at each level. Both paths are single lines through the tree — never branches — and a single line through a binary tree of nodes is at most the height of the tree long. That is the whole reason both operations share the same cost: each does constant work at every level it touches, and the number of levels is .

The landmark to notice is that both bubbles stop early in most cases: a new element that lands near its correct spot climbs only one or two levels, and a replacement root that is large enough sinks only one or two levels. The bound is the worst case (a full vertical trip), not the common case. The one-sentence takeaway: insertion and removal are the same procedure facing opposite directions, and both are bounded by the height of the tree.

6.1.4 Student Questions and Answers

Q: How do we insert an element into a heap? A: We push the new element into the first available leaf position, then run upheap to bubble it up. The time of upheap is — read "big O of h" — and since is the height of the binary tree, this is . The placement itself is constant time; the bubble does the rest.

Q: How do we remove an element from a heap? A: We remove the root element — that is the only element we remove. We exchange the root with the last leaf element, remove the last element, and then heapify the remaining tree. Again the cost is .

6.1.5 Exam Notes

Exam note: recall questions on the insertion steps, the removal steps, and the upheap and downheap complexities should be answered instantly — the professor said there is nothing to think about if you have understood. Know both procedures cold, and know that each costs .

Recap + Bridge: insertion and removal are the two bubbles — upheap for new leaves, downheap for the replacement root — each costing the height of the tree. But both procedures assumed a heap already existed. The session's real question comes next: how do we build a heap from scratch, and how much does that cost?

Real-world connection: these two bubbles are the engine inside every heap consumer. Operating system schedulers keep ready processes in a priority heap and constantly insert new processes (upheap) and remove the highest-priority one (downheap); network routers do the same for packets waiting at a congested link. The same pair of operations will power heapsort (Section 6.6) and Dijkstra's shortest-path algorithm later in the course, which repeatedly extracts the smallest remaining distance from a heap.

6.2 Fixing a Broken Heap

6.2.1 The Fix-Down Process

Hook: Suppose only one node of a binary tree is out of place — the root is smaller than its children, while both subtrees are perfect max heaps. Can a single downward pass of exchanges repair the whole tree, and how much time does that pass take?

Consider a binary tree with left and right subtrees that are already heaps, but the root is not. What kind of heaps are the two subtrees? Max heaps — each is a valid max heap on its own. The whole tree is not a heap, only the subtrees are. How do we convert the whole tree into a heap? The tool is heapify, and we already met it last session: since this is a max heap, we compare the two children, find the maximum element among them, and exchange the root with that maximum; then we keep exchanging until the misplaced element finds its proper place.

Formalize. The fix-down procedure takes a node whose children are both valid max heaps and repairs the path below it. For a node at index with left child and right child :

  1. Compare and ; let largest be the index of the larger child (the children themselves may be missing or beyond the heap).
  2. If the larger child is bigger than , exchange with that child.
  3. Move to the position the old value landed in and repeat, until the value is not smaller than either child or reaches a leaf.

Each single exchange is a constant-time step — one comparison plus one swap — and the number of exchanges is bounded by the height of the node we start from. So heapify at the root costs , where is the height of the tree. In algorithmic form this is exactly Max-Heapify:

Max-Heapify(A, i)
  l = 2i            // left child
  r = 2i + 1        // right child
  if l <= heap-size and A[l] > A[i]:  largest = l
  else:                                largest = i
  if r <= heap-size and A[r] > A[largest]:  largest = r
  if largest != i:
    exchange A[i] with A[largest]
    Max-Heapify(A, largest)

In the example shown during the session, the root holds 2 and its children hold 14 and 8. The larger child is 14, so 2 is exchanged with 14 — not with 8 — and the exchange repeats as 2 travels downward. One exchange at this level is not the end: 2 must keep descending until it lands at a leaf, so there is one more step beyond the first swap.

Worked example. Suppose the broken tree is this max heap with a wrong root:

        2
       / \
      14  8
     / \ / \
    6  4 7  1

Both subtrees are valid max heaps (14 ≥ 6, 4 and 8 ≥ 7, 1), but the root 2 violates the heap property.

Step 1 — Compare the children and take the maximum. The children of the root are 14 and 8. The maximum is 14, so exchange the root with 14 — not with 8.

        14
       / \
      2   8
     / \ / \
    6  4 7  1

Step 2 — Repeat where the 2 landed. The 2 now sits where 14 was. Its children are 6 and 4. The maximum is 6, which is greater than 2, so exchange 2 with 6.

        14
       / \
      6   8
     / \ / \
    2  4 7  1

Step 3 — Stop. The 2 has reached a leaf, so no further exchange is possible. The tree is now a valid max heap: 14 ≥ 6, 8; 6 ≥ 2, 4; 8 ≥ 7, 1.

Sense-check. The 2 made two exchanges — one per level it descended — and the path length from the root to a leaf was exactly two. The cost of the whole fix was the height of the tree, , and every decision along the way was "compare children, take the maximum, exchange with that one."

6.2.2 Assumptions and Scope

Assumption: Heapify assumes both subtrees of the broken node are already valid heaps of the same kind (both max heaps, or both min heaps). The procedure repairs the single path below the broken node; it does not search the rest of the tree. Scope: The identical procedure works for a min heap by taking the smaller child at each step (Min-Heapify). If the root is already larger than both children, heapify does no exchange at all and runs in . Heapify repairs one broken node — building a whole heap from a raw array is a different job, done by Build-Max-Heap (Section 6.3).

6.2.3 Visual Intuition — One Path, Downward

Picture the misplaced value as a stone dropped into a funnel of comparisons. At each level it faces exactly two candidates — its two children — picks the heavier one, and swaps places with it. The stone never goes up, and it never branches: it follows one straight line from its starting node down to a leaf. That line is the entire cost of heapify. The landmark is the end of the line: the stone stops either at a leaf or at a node where both children are smaller, and at that moment every parent on the path is larger than its children again. The one-sentence takeaway: heapify is a single downward path, and its time is the length of that path.

6.2.4 Common Pitfalls

  • Exchanging with the wrong child. In a max heap the root must be exchanged with the larger child. Swapping with 8 instead of 14 would leave the new root smaller than 14, so the heap property would still be violated at the root — the fix would not fix anything.
  • Stopping after one exchange. The misplaced value may still violate the heap property at its new, deeper position. The 2 in the worked example needed a second exchange; stopping early leaves a broken heap.
  • Expecting heapify to repair the whole tree. Heapify repairs one node and the path below it. If several nodes are out of place, heapify alone is not enough — you need the bottom-up construction of Section 6.3.
  • Mixing up the two bubbles. Upheap (used in insertion) moves a value upward by comparing with parents; heapify moves a value downward by comparing with children. They are mirror images, and the wrong choice silently corrupts the heap.

6.2.5 Student Questions and Answers

Q: Why is fourteen exchanged and not eight? A: Because this is a max heap, so a parent must be larger than its children. Before exchanging, we compare the two children, 14 and 8, and pick the maximum — fourteen. The smaller child, eight, stays where it is. Exchanging with the larger child keeps both subtrees valid, so every later step of the heapify stays correct. If we exchanged with eight instead, the new root (eight) would still be smaller than fourteen, and the heap property would still be violated.

6.2.6 Exam Notes

Exam note: the question "why is 14 exchanged and not 8" is exactly the kind of conceptual question that can appear — the answer is the max-heap rule: compare the children first, find the maximum, and exchange with that.

Recap + Bridge: heapify repairs one broken node in by sending the misplaced value down a single path, always trading with the larger child. This per-node procedure is the building block of the next question: how do we turn a whole raw array into a heap — and is repeated insertion really the best way?

Real-world connection: heapify is the inner workhorse of every heap-based system — each extract-min in a priority queue and each round of heapsort runs a heapify. In network routing, Dijkstra's algorithm (later in the course) relies on exactly this operation: every time the shortest distance to a node is finalized, the distance heap must be repaired, and one heapify call per relaxation keeps the whole algorithm fast.

6.3 Constructing a Heap

6.3.1 The First Approach: Repeated Insertion

Given a heap, we know how to insert elements and how to remove the maximum. But how do we build a heap for a raw array of values? The most direct plan is repeated insertion: take the values one at a time and push them into an empty heap. Here is the naive analysis of that plan: each of the insertions triggers a heapification, and heapify costs , so the total cost of building the heap this way comes to . That is the first, careless estimate — and it is the wrong one. The naive repeated-insertion construction costs times , which is , but the professor stopped the class on this answer: the analysis is too loose, and a better look at the same process shows a much better bound.

6.3.2 A Full Walkthrough

Let's test that estimate with a concrete case. We insert eight elements — 13, 14, 15, 18, 11, 12, 17, 16 — one by one into a max heap, heapifying after each insert.

Worked example. Track the array after each insertion, and count the exchanges.

Insert Heap after the insert Exchanges Why
13 13 0 first element, becomes the root
14 14, 13 1 14 is larger than parent 13
15 15, 13, 14 1 15 is larger than parent 14
18 18, 15, 14, 13 2 18 swaps with 13, then with 15
11 18, 15, 14, 13, 11 0 parent 15 is already larger
12 18, 15, 14, 13, 11, 12 0 parent 14 is already larger
17 18, 15, 17, 13, 11, 12, 14 1 17 swaps with parent 14; 18 is already larger
16 18, 16, 17, 15, 11, 12, 14, 13 2 16 swaps with 13, then with 15

Reading the first insertions step by step: 13 becomes the root. Insert 14 as the left child — we fill the tree level by level, left to right, so 14 goes left, not right — and 14 is larger than 13, so 13 and 14 exchange: one exchange. Insert 15: it violates the heap property, so 14 and 15 exchange — one more exchange. Insert 18: 18 is placed below 13, and since 18 is larger, 18 exchanges with 13; but 18 is still larger than 15, so a second exchange puts 18 at the root — two exchanges for this insert. Insert 11: 11 is checked against 15 and needs no exchange — the heap property already holds. Insert 12: no violation either. Insert 17: 17 violates the property, so 17 exchanges with its parent 14 — and then stops, because 17 is smaller than 18 — one exchange. Finally insert 16: 16 exchanges with 13, then with 15, because 15 is smaller — two exchanges.

Final heap, level by level: 18, 16, 17, 15, 11, 12, 14, 13. The tree view makes the result easy to check:

            18
       /          \
    16              17
   /  \            /  \
 15    11        12    14
 /
13

Sense-check. Every parent is larger than its children: 18 ≥ 16, 17; 16 ≥ 15, 11; 17 ≥ 12, 14; 15 ≥ 13. The heap property holds at every node, and the total number of exchanges across all eight inserts is only 7 — far less than the level-steps the naive analysis was charging.

Counting the exchanges per inserted element shows that the naive analysis is loose: some inserts need no exchange at all, some need one, some need two, and none needed the full height of the tree. Yet the analysis charged every insert a full heapify.

6.3.3 The Tighter View

The estimate above is loose because heapify at a node costs only as much as the height of that node, not the height of the whole tree. The heights of most nodes are small: out of nodes, about are leaves at height 0, about sit at height 1, and so on. Only a handful of nodes — the root and the level just below it — have large heights. So the analysis must charge each node only its own height, and then add up over the levels. That single change of perspective — charging each node its own height instead of the tree's height — is what the rest of this section develops.

6.3.4 A Second Walkthrough

The same eight elements can be read in a second way — as a raw initial array rather than a sequence of insertions. The processing starts with the last parent — the rightmost parent in the tree — and works leftward: the loop runs from down to 1.

Why start at the last parent? The leaf nodes are already heaps by themselves — a single node always satisfies the heap condition, since it has no children to violate it — so the last positions of the vector need no work at all. Processing starts at the node just before them, the rightmost parent at index , and moves left until the root. If a subtree rooted at a node does not satisfy the heap condition, keep exchanging it with its largest child until the condition holds.

Worked example — the same eight elements, viewed bottom-up. Initialize the structure with the keys in the given order: 13, 14, 15, 18, 11, 12, 17, 16 at locations 1 through 8. The leaves — positions 5 to 8, holding 11, 12, 17, 16 — are already heaps by themselves, so the work starts at position .

Position Node Children Exchanges Result
4 18 16 (position 8) 0 18 ≥ 16: already a valid max heap
3 15 12 (position 6), 17 (position 7) 1 17 comes up, 15 comes down; 17 has only one level below, so one exchange is the maximum
2 14 18 (position 4), 11 (position 5) 2 18 is greater than 14, and 16 is greater than 14 — so 14 descended two levels
1 13 18 (position 2), 17 (position 3) 3 18, 16, and 14 are all greater than 13 — the full height of the tree

Final heap: 18, 16, 17, 14, 11, 12, 15, 13.

            18
       /          \
    16              17
   /  \            /  \
 14    11        12    15
 /
13

Sense-check. Every parent is larger than its children: 18 ≥ 16, 17; 16 ≥ 14, 11; 17 ≥ 12, 15; 14 ≥ 13. As we go up the tree, the exchanges per node go up by one — 0, 1, 2, 3 — but the number of nodes at each level is halved — 4 leaves, 2 parents above them, 1 node above that, 1 root. That trade-off is the whole secret of the tight analysis.

A second example makes the same point with ten values. The array is written into the tree shape with the values in the given order at locations 1 through 10 — 4, 1, 3, 2, 16, 9, 10, 14, 8, 7:

            4
       /          \
     1               3
   /   \           /   \
  2    16         9    10
 / \   /
14 8  7

The last five positions — 9, 10, 14, 8, 7 — are the leaf nodes, and they need no work at all. The instructor named exactly these values as the ones that are "already heaps", and the rightmost parent — the first node processed — is 16 at index , with the single child 7 below it. Processing from position 5 down to 1:

Worked example — bottom-up heapify of 4, 1, 3, 2, 16, 9, 10, 14, 8, 7.

Position Node Children Exchanges
5 16 7 (position 10) 0 — 16 ≥ 7 already
4 2 14 (position 8), 8 (position 9) 1 — 2 swaps with 14, lands at a leaf
3 3 9 (position 6), 10 (position 7) 1 — 3 swaps with 10, lands at a leaf
2 1 14 (position 4), 16 (position 5) 2 — 1 swaps with 16, then with 7
1 4 16 (position 2), 10 (position 3) 3 — 4 swaps with 16, then 14, then 8

Final heap: 16, 14, 10, 8, 7, 9, 3, 2, 4, 1.

            16
       /          \
    14              10
   /   \           /   \
  8     7         9     3
 / \   /
2  4  1

Sense-check. 16 ≥ 14, 10; 14 ≥ 8, 7; 10 ≥ 9, 3; 8 ≥ 2, 4 — every parent dominates its children. The exchange counts read 0, 1, 1, 2, 3 from the bottom up: again the work per node grows by one per level, while the number of nodes per level halves.

6.3.5 The Algorithm in Full

In algorithmic form, the construction is called Build-Max-Heap. Initialize the structure with the keys in the given order — the array as it stands is already the heap-shaped tree, and all the exchange operations happen on the vector locations; there is no tree object anywhere. Then fix the heaps from the bottom up:

Build-Max-Heap(A)
  A.heap-size = A.length
  for i = floor(A.length / 2) down to 1:
    Max-Heapify(A, i)

Build-Max-Heap loops from — the floor of over 2 — down to 1, calling Max-Heapify at each index. Start with the last parental node and fix the heap rooted at it: if it does not satisfy the heap condition, keep exchanging it with its largest child until the condition holds — then move to the previous parent, and so on down to index 1. The leaf nodes are already heaps by themselves, so we never call heapify on them.

6.3.6 The Tight Analysis

The tighter analysis relies on two facts about an -element heap: its height is , and there are at most

— read " divided by 2 to the power plus 1" — nodes at any height . At the leaves — height 0 — there are about nodes. Max-Heapify takes time for the nodes one level above the leaves, for the nodes two levels above, and for the nodes levels above the leaves; at the root it takes , the height of the tree.

Summing these over the levels gives a linear total. At height there are at most nodes, and each costs :

The sum is a standard series: writing in the identity gives . So the total work is : building a heap takes linear time, , not .

The reason the sum comes out linear, in the professor's words: the number of nodes where a height-2 repair is needed is smaller than the number where a height-1 repair is needed, and that trade-off — fewer nodes as the height grows — is what makes the sum come out linear. A separate reference document with the full mathematical proof is available for anyone who wants it; it is optional reading.

6.3.7 Assumptions and Scope

Assumption: The analysis assumes the array fills positions 1 through level by level, so the vector really has the complete-tree shape. The bound holds for any input values, because every run of Build-Max-Heap does the same heapify calls — the figure is the algorithm's running time, not a lucky-case estimate. Scope: The same construction with Min-Heapify builds a min heap in the same linear time. Note that the heap built from a set of keys is not unique: the same values can form different heaps depending on the order they are processed in — the professor made the point that more than one heap can be constructed with the same nodes. The bound also assumes comparisons between keys are constant-time.

6.3.8 Common Pitfalls

  • Quoting for building a heap. This is the naive analysis — the exact mistake the class made and the professor corrected. The answer is always , linear time.
  • Writing the loop as . The loop runs from down to 1, because the last positions are leaves. The "minus one" slip comes from 0-based thinking; in the 1-based vector used throughout, the last parent is at index .
  • Calling heapify on leaves. Leaves are already heaps by themselves; heapifying them wastes the work the tight analysis saved.
  • Confusing build-heap with the per-operation cost. Build-Max-Heap is total; a single insertion or extraction from the finished heap is still .

6.3.9 Student Questions and Answers

Q: Should the loop start at minus 1 or at ? Somebody asked whether it is . A: It runs from down to 1. The last positions of the vector are leaf nodes, so the first parent — the last parental node — is at index . Writing minus 1 there was a slip in the example, a mistake we corrected; the rule is from down to 1.

Q: Is the best-case time for building a heap? A: No — is the running time of the algorithm, not a best case. The first estimate came from a naive analysis that charged every heapify the full tree height. The careful analysis shows the real cost is linear, . It was an error in the first analysis, not a special case: first we made a mistake, then we rectified the mistake in analyzing the algorithm.

6.3.10 Exam Notes

Exam note: the correct answer for building a heap is — a linear-time algorithm — never . The loop bounds matter too: from down to 1. The mathematical proof of the bound is optional reference material; it will not be asked in the exam, because with a search engine the proof would simply be copied out. Conceptual questions in the style of "why is 14 exchanged and not 8" (Section 6.2) and the exchange-versus-height pattern of this section are the fair game.

Recap + Bridge: repeated insertion would build a heap in , but bottom-up construction — charging each node only its own height — builds the same heap in . This linear-time build is the first step of heapsort, which turns the heap into a sorting method (Section 6.6).

Real-world connection: the linear-time build matters wherever a priority queue must be created from a batch of data instead of being filled one element at a time — for example, when an event-driven simulator loads thousands of pending events at startup, or a router initializes its scheduling queues from a snapshot of waiting flows. Cutting the build from to is exactly the kind of "day and night" difference the professor promised at scale.

6.4 The Priority Queue ADT

6.4.1 Definition and Main Methods

Hook: A queue serves the person who waited longest. But what if the person who matters most is not the one who arrived first — and what if priorities keep changing? A priority queue is the container built for exactly that situation: it serves the entry with the best key, whatever that entry is.

A priority queue is a container: every entry has an associated key, provided at the time the entry is inserted. The name comes from the fact that the keys determine which entries get removed, and in which order: the key of an entry is the priority of that entry. Each entry is a key–element pair — the key carries the priority, the element carries the data being served. The keys determine which entries get removed, and in which order: remove the entry whose key is smallest (a min-oriented priority queue) or largest (a max-oriented one).

The two main methods are insertItem, which inserts an item with key and element , and removeMin, which removes the item with the smallest key and returns its element. The remove-min operation removes the item with the smallest key and returns its element — nothing else in the queue is removed.

The remaining methods are standard: minKey returns the smallest key of an item but does not remove it; minElement returns the element of the item with the smallest key without removing it; size reports how many items the queue holds; isEmpty tells whether the queue is empty. All of these are implementation-based — you can implement them however you want — but these are the general, generic methods. minKey and minElement are the "look, don't touch" pair: they let an application peek at the next entry to be served without changing the queue.

6.4.2 Applications

Real-world: standby flyers. When airline seats are sold out and passengers sit on the waiting list, their priority is measured by the fare paid, the frequent-flyer status, or the check-in time, and the standby list behaves exactly like a priority queue: each waiting passenger is an entry, the priority factors are the key, and the passenger with the best combination is served first — not the one who joined the list first.

Real-world: sealed-bid auctions. Randomly arriving customers are privately informed about their own processing time and make bids upon arrival; a customer gets priority over all other customers waiting in the queue who made lower bids. The bid decides the order in which waiting customers are served — the higher the bid, the earlier the service.

Real-world: stock markets. When there are multiple possible buyers for a stock, the tie is broken by choosing the buyer that placed the bid earliest — or the lowest bid, depending on the model and the exchange's rules. Either way, the matching engine is a priority queue: buy and sell orders are entries, the price (and time of arrival) is the key, and the engine always matches the best-keyed orders first.

6.4.3 Total Order and Sorting with a Priority Queue

Like the heap, the priority queue requires a total order relation on keys — recall the three rules of total order studied just before heaps: every two keys are comparable (connected), the relation is transitive, and it is antisymmetric, so comparisons never disagree. Keys can be arbitrary objects on which an order is defined; two distinct items may carry the same key, and how equal keys are handled is a decision we make when we choose the implementation.

Sorting with a priority queue: given a collection of elements that can be compared according to the total order relation, insert them one by one with a series of insertItem operations, then remove them with a series of removeMin operations. This rearranges the collection in non-decreasing order. It is always safer to say non-decreasing rather than increasing, because multiple elements can have the same key; increasing order would forbid equal keys, while non-decreasing accommodates them.

Worked example. We sort a small collection with a priority queue by inserting all the items first and then removing the minimum repeatedly. Take the five items (5, A), (2, B), (7, C), (2, D), (9, E), where the first value is the key.

Step 1 — Insert all items. After insertItem((5, A)), insertItem((2, B)), insertItem((7, C)), insertItem((2, D)), insertItem((9, E)), the queue holds all five items, keyed 5, 2, 7, 2, 9.

Step 2 — Remove the minimum repeatedly. removeMin first returns the item with key 2 (say B), then the other key-2 item (D), then key 5 (A), then key 7 (C), then key 9 (E).

Step 3 — Read the output. The removed keys come out as 2, 2, 5, 7, 9 — non-decreasing. Note the two equal keys 2 appear in the output; a claim of "increasing order" would be false here, because 2, 2 is not strictly increasing.

Sense-check. Every removeMin returned the smallest remaining key, so the output must be sorted from smallest to largest, with equal keys allowed side by side — exactly the non-decreasing order the professor insists on.

6.4.4 Visual Intuition — A Line That Re-Sorts Itself

Picture a service desk with a single queue. Each customer carries a priority number, and the queue's rule is simple: whoever holds the smallest number is served next. When a new customer arrives, they do not stand at the back — the queue reorders so that the smallest number is always at the front, ready to be served. The two operations map cleanly onto this picture: insertItem is a customer joining the queue, removeMin is the customer at the front being served and leaving. minKey is the same as glancing at the front of the line without serving anyone. The landmark to watch is what happens with equal numbers: two customers with the same priority can stand anywhere relative to each other unless the implementation adds a rule (Section 6.5 shows the tie-break). The takeaway: a priority queue is a line whose order is decided by keys, not by arrival time.

6.4.5 Common Pitfalls

  • Saying "increasing order" instead of "non-decreasing order." Equal keys make "increasing" false; non-decreasing is always safe and is the phrasing the professor requires.
  • Confusing removeMin with minKey. removeMin takes the smallest-key item out of the queue; minKey only reports the smallest key. Using the wrong one either loses data or never serves anyone.
  • Forgetting the key is supplied at insertion time. The key is attached when the item enters the queue; the priority cannot be discovered later from the element alone.
  • Assuming equal keys have a defined order. Two items with the same key have no inherent order in the ADT — the implementation decides, and applications that care (like a FIFO within a priority) must supply a tie-break.

6.4.6 Student Questions and Answers

Q: Should we say increasing order instead of non-decreasing? A: Always mention non-decreasing order. If multiple elements have the same key, the output cannot be strictly increasing; non-decreasing order is the correct phrasing, and it is always safe to write. The professor's exact rule: "always mention non-decreasing rather than increasing, because multiple elements can have the same key."

6.4.7 Exam Notes

Exam note: when describing the result of sorting with a priority queue — or any sorting that allows equal keys — always phrase the output as non-decreasing order, to accommodate equal keys. The main methods insertItem and removeMin, and the peek methods minKey and minElement, are standard recall items.

Recap + Bridge: a priority queue is a container of key–element pairs served in key order, with a total order on keys and duplicate keys allowed. Nothing in the ADT says how fast the operations are — that depends on the implementation, which is exactly the question of the next section: sorted sequence, unordered sequence, or heap.

Real-world connection: beyond the professor's standby-flyer, auction, and stock-exchange examples, priority queues run the schedulers inside operating systems (the ready queue of processes is keyed by priority), hospital emergency departments triage patients by severity, and network routers give delay-sensitive packets priority over bulk traffic — every one of these is the same key–element container, served by insertItem and removeMin.

6.5 Ways to Implement a Priority Queue

6.5.1 Sorted Sequence and Sorted Array

A sorted list or a sorted array is the obvious first idea: while inserting, you pay the cost of inserting at the correct position, and while removing, you remove from one end of the list. That trades cost for simplicity — and it makes the insertion step expensive. If we insert elements one by one into a sorted array, each insertion can cost because all larger elements must shift, so we end up spending total, and a sorted sequence is not a good starting point.

Why does insertion into a sorted array cost ? The array is kept ordered at all times, so a new key must land between its smaller and larger neighbours. Finding the spot is fast, but making room is not: every larger element must shift one position to the right. If the new key is the smallest so far, every element shifts — the full — and that happens over and over across insertions, for a total of . The removal side, by contrast, is trivial: the smallest item sits at one end, so removeMin just takes it — .

6.5.2 Unordered Sequence

An unordered sequence is the opposite: insertion is cheap — , we simply append — but finding the minimum requires a scan of the whole sequence, , and if we repeat that scan times we pay again. Retrieval is what hurts. So both obvious sequence implementations are quadratic over a full workload; the difference is only where the pain lands. A sorted sequence pays on the way in (insert), an unordered sequence pays on the way out (find the minimum). Neither can be the foundation of a fast priority queue.

6.5.3 Heap-Based Implementation

A heap is the balanced choice. Keep a min-heap of key–element pairs — you need to keep track of the position of the last node so the next available location is always known. A heap-based priority queue gives for both insert and remove-min. With a sorted array, that same insertion would have cost — that is exactly why we do not use a plain array for a priority queue.

Worked example. Suppose the priority queue is a min-heap with keys 2, 5, 6, 9, 7 (elements ignored for clarity), and we insert the item (3, feb) — key 3, element "feb".

       2
      / \
     5   6
    / \
   9   7

Step 1 — Place at the next available location. The heap is complete, so the next available location is the first free slot after the last leaf — the left child of 6. Put (3, feb) there.

Step 2 — Heapify upward. The new key 3 is smaller than its parent 6, so exchange 3 and 6: the heap now reads 2, 5, 3, 9, 7, 6.

Step 3 — Check the next level. The new key 3 is now at position 3, whose parent is the root 2. Since 3 is larger than 2, no exchange is needed — the heap property holds.

       2
      / \
     5   3
    / \ / \
   9  7 6

Final answer: the item (3, feb) is stored, and the heap keys read 2, 5, 3, 9, 7, 6. The insertion made one exchange and two comparisons — at most the height of the tree, so . Sense-check. A min-heap parent must be smaller than its children: 2 ≤ 5, 3; 5 ≤ 9, 7; 3 ≤ 6 — all hold. With a sorted array, the same insertion would have shifted up to six elements — .

6.5.4 Comparison and Choice

Comparison of the three implementations:

Implementation insertItem removeMin Weakness
Unordered sequence scanning for the minimum
Sorted sequence shifting elements on insert
Heap must track the last node

With an unordered sequence, insert is and removeMin is ; with a sorted sequence, insert is and removeMin is , because the smallest item sits at the first (or last) location; with a heap, both operations are . The heap gives a balanced, average performance of everywhere, while the sequence implementations are each excellent at one operation and poor at the other.

No implementation wins everywhere, so you have to choose between the two based on the application: if your application needs more inserts, choose the heap; if it needs more extractions, choose the sorted sequence. Make a balanced decision — when is large, and make a difference of day and night; they are not anywhere close. The professor's closing line on this comparison: don't think and are somewhere close — for very large the difference is like day and night, so you must choose the implementation by the application's operation mix.

6.5.5 Assumptions and Scope

Assumption: All three implementations assume keys can be compared by a total order, and that a single comparison costs constant time. The heap implementation also assumes the heap shape is maintained — which is why the position of the last node must be tracked. Scope: The asymptotic table describes behaviour as grows; for tiny queues, constant factors (and the overhead of maintaining a heap) can matter more than the versus distinction. The professor's "day and night" argument is about large — that is where choosing the balanced implementation pays.

6.5.6 Student Questions and Answers

Q: When we use a heap for a priority queue, how do we satisfy the FIFO property of items with the same key? A: You need to make a balance — you have to store the order in which the items were included. Add one more variable that keeps the time of insertion or a sequence number, and tie-break equal keys by that value. Whether the equal-key items sit on the left or the right of the heap is an implementation detail; the point is to distinguish which equal key came first. If your application expects the same key to arrive repeatedly, think about that application and pick the most feasible option — a time stamp or an order number.

6.5.7 Exam Notes

Exam note: expect the three-implementation comparison — unordered sequence (insert , removeMin ), sorted sequence (insert , removeMin ), heap (both ) — and the choice rule: more inserts means the heap, more extractions means the sorted sequence. When is large, and differ by day and night, so make the balanced implementation choice with that in mind.

Recap + Bridge: the priority queue ADT is implementation-agnostic, but the implementation decides the cost: sequences are unbalanced, the heap is balanced at . The heap's balanced cost is what makes the next idea possible — turning the heap itself into a sorting algorithm, heapsort.

Real-world connection: real priority queues live inside operating-system schedulers and network routers, where the workload is a mix of inserts and extractions, so the heap is the default choice; a sorted sequence shows up where removals dominate — for example, a list of deadlines that only ever removes the earliest one, like expiry timers that fire in order.

6.6 Heapsort

6.6.1 The Algorithm

Hook: You can build a heap in and extract the maximum from it in . Combine those two facts into a loop — extract, place at the back, repair — and the heap becomes a sorting method that sorts the array in place in . That method is heapsort.

Heapsort turns the heap into a sorting method. Heapsort sorts an unsorted array : the output is the same array modified to be sorted from smallest to largest. The input is the array in any order; the output is the very same array, now in non-decreasing order — no second array is created.

Purpose: convert the extract-max machinery of a max heap into a general sorting method. Inputs: an array in arbitrary order. Outputs: the same array , sorted from smallest to largest.

Steps:

  1. Build a max heap from the array time, the linear-time Build-Max-Heap of Section 6.3. The array itself is the heap; no extra storage.
  2. For down to 2: exchange — the root, which holds the largest remaining value — with , the last element of the current heap. This moves the current maximum to the back of the array, into the sorted portion.
  3. Shrink the heap by one and call Max-Heapify on the shortened heap. The exchanged value is stored at the back of the array: we start filling the sorted output from the last location of the same array, so the sorted portion grows at the end while the heap shrinks.

Why does this sort? The heap property guarantees that the root is the largest remaining element. Each round takes that largest element and parks it at the end of the current heap region — which is exactly its final position in the sorted array. The heapify call then promotes the next largest to the root, ready for the next round. After rounds every element has been moved into the sorted tail, and the array is fully sorted.

6.6.2 A Worked Example

Start from a heap already built — building it took time, the linear-time algorithm we studied. The heap is the ten-element max heap 16, 14, 10, 8, 7, 9, 3, 2, 4, 1, so the roots run 16, 14, 10 across the first rounds.

Worked example. The heap is 16, 14, 10, 8, 7, 9, 3, 2, 4, 1. Each round: exchange the root with the last element of the current heap, shrink the heap, and Max-Heapify.

Round Exchange Heap after Max-Heapify Sorted tail
1 16 ↔ 1 14, 8, 10, 4, 7, 9, 3, 2, 1 16
2 14 ↔ 1 10, 8, 9, 4, 7, 1, 3, 2 14, 16
3 10 ↔ 2 9, 8, 3, 4, 7, 1, 2 10, 14, 16
4 9 ↔ 2 8, 7, 3, 4, 2, 1 9, 10, 14, 16
5 8 ↔ 1 7, 4, 3, 1, 2 8, 9, 10, 14, 16
6 7 ↔ 2 4, 2, 3, 1 7, 8, 9, 10, 14, 16
7 4 ↔ 1 3, 2, 1 4, 7, 8, 9, 10, 14, 16
8 3 ↔ 1 2, 1 3, 4, 7, 8, 9, 10, 14, 16
9 2 ↔ 1 1 2, 3, 4, 7, 8, 9, 10, 14, 16

Walking the first two rounds in detail. Round 1: the root is 16. Exchange 16 with the last element 1 — a constant-time swap — and remove 16 from the heap: it is placed at the last location of the sorted array. Heapify the remaining heap: 1 bubbles down and 14 comes to the root. Round 2: exchange 14 with the last element of the shrunken heap (which is now 1), place 14 just before 16 in the sorted portion, heapify again: now 10 comes to the root. Continue the same pattern — exchange the root with the last element, heapify — until every element has been moved into the sorted portion.

Final sorted array: 1, 2, 3, 4, 7, 8, 9, 10, 14, 16. Sense-check. The largest value, 16, went to the last position; each round then placed the next largest just before the sorted tail; after nine rounds the single element left is the smallest, 1, already in place. The sorted array reads exactly the heap's values in ascending order, and every round performed one constant-time exchange plus one heapify.

6.6.3 Time and Space

Time: building the heap costs ; the loop runs times and each Max-Heapify costs , so the total is

Heapsort totals for the build plus for the loop, giving . The linear build step cannot rescue the total: although building the heap is linear, removing the maximum one by one forces heapifications, and that part cannot be helped. The runtime of the whole algorithm is — a summary the professor delivered with a mock plea not to ask why again: the build is linear, the removal loop is not, and the loop dominates.

Space: the swap-with-the-last-element trick keeps everything in one array — the sorted portion grows at the back of the same array — so the sort can be done in place. Building the heap is also done in place, using the same array. If instead you insist on maintaining a separate sorted list, you would need one more array of the same size.

6.6.4 Visual Intuition — The Array with Two Regions

Picture the array as a single strip with a moving boundary. Everything to the left of the boundary is the heap — a pyramid standing on its vector base, with the current maximum at its peak. Everything to the right of the boundary is the sorted tail — flat, finished, in ascending order. Each round does three things: the peak of the pyramid is lifted off and laid down just to the right of the boundary; the boundary shifts one position left; and the pyramid is re-carved (heapify) so a new peak appears. The landmark is the boundary itself: after round , the last positions hold the largest elements in sorted order, and the heap region is exactly elements tall. The takeaway: one array hosting two structures — a shrinking heap and a growing sorted tail — with no second array anywhere.

6.6.5 Common Pitfalls

  • Calling the heap itself a sorting algorithm. The heap is a data structure; heapsort is the sorting algorithm built on it. The professor explicitly corrected this confusion in the session.
  • Forgetting to shrink the heap before heapifying. After the exchange, the last element belongs to the sorted tail; if heapify is allowed to touch it, the sorted portion gets re-arranged and the sort breaks.
  • Explaining the runtime as "just ." The bound comes from two parts: the linear build plus heapifies, each — the exam asks for that justification, not the slogan.
  • Expecting the max-heap version to produce descending order. Exchanging the root with the back of the array and filling the tail from the end produces ascending order; the descending variant would store the extracted maxima in a separate front-loaded list.

6.6.6 Student Questions and Answers

Q: Is a heap a sorting algorithm? A: No — the heap by itself is a data structure, not a sorting algorithm. Heapsort is the sorting algorithm that builds on the heap. Keep this distinction; later in the course, when we study sorting algorithms properly, heapsort will come back.

Q: Do we need extra space for the sorted array? A: No — this can be done in place: the root and the last element exchange, and the sorted values land back in the same array, starting from its last location. Building the heap is in place too. Alternatively, construct a min heap and run the same process, storing the sorted elements from location 1 forward — same concept, nothing changes.

6.6.7 Exam Notes

Exam note: heapsort is on the radar for the sorting unit — "keep this in mind" — and the worked pattern here is the one to reproduce: build the heap (linear), then exchange root with last element and heapify, times. Expect to be asked why the runtime is : the build is , the loop is heapifies at each, and the loop dominates.

Recap + Bridge: heapsort combines the linear build with the extract-max loop to sort in place in . The heap's remaining helper operations — parent and child navigation, increase key, extract max — are the tools the practice exercises build on, and they come next.

Real-world connection: heapsort's guarantee of in every case — no bad inputs, no extra memory — makes it the choice for systems where memory is precious and worst-case behaviour matters, such as embedded controllers and real-time systems. The same extract-max loop also answers selection-style questions (the first largest elements of a stream) by stopping early, which is why priority-queue machinery appears inside analytics systems that rank incoming records.

6.7 The Helper Operations of a Heap

6.7.1 Navigation within the Vector

Given a vector-based heap and an index , the three navigation rules are constant-time operations. The parent of the node is at — the floor of divided by 2, written in the standard form the reference text uses; the left child is at ; the right child is at — "two plus one". These are all operations — simple index arithmetic on the vector, with no tree traversal.

The tree picture is only a way of seeing the vector; the vector locations are the reality. The three rules are exactly what make the heap work without pointers: instead of following a link from one node to another, we multiply or divide the index, and the arithmetic lands on the neighbouring node. In algorithmic form:

Parent(i):  return floor(i / 2)
Left(i):    return 2i
Right(i):   return 2i + 1

Note the direction of these rules: a node's parent index is roughly half its own index, and its children are about double — so a path from a leaf to the root shrinks the index by half at every step, which is why any climb or descent through the heap touches at most nodes.

6.7.2 Heap Increase Key

Heap increase key handles the case where the value at a node goes up. Given an array representing a heap, an index , and a new key greater than , we keep exchanging the node with its parent while the parent is smaller, until the node finds its place — an upward bubble, exactly like upheap. The cost is because it is a heapification process.

Why does raising a value only need an upward bubble? Raising a key can only violate the heap property in one direction: a parent is required to be at least as large as its children, so a node that just got bigger can only be too big for its own parent — its children are untouched, since the value only grew. Walking upward, exchanging with any smaller parent, restores the property along the whole path.

Worked example. Take the heap built earlier in the session — 18, 16, 17, 15, 11, 12, 14, 13 — and increase the key 11 (at position 5) to 17.

            18
       /          \
    16              17
   /  \            /  \
 15    11        12    14
 /
13

Step 1 — Compare with the parent. The node is at position 5, so its parent is position , holding 16. The new value 17 is greater than 16, so exchange: position 2 takes 17 and position 5 takes 16.

Step 2 — Compare with the next parent. The node is now at position 2, whose parent is position 1, holding 18. Since 18 is not smaller than 17, the exchange stops.

Result: 18, 17, 17, 15, 16, 12, 14, 13. Sense-check. 18 ≥ 17, 17; 17 ≥ 15, 16; 17 ≥ 12, 14 — every parent dominates its children again. One exchange and two comparisons, well within the budget; and note the two 17s side by side — a heap does not require distinct keys.

Real-world: the price of an item in an auction can be increased; the value of a stock can be increased; the priority of a customer suddenly increases because they decide to pay more. In each case the stored key rises, and increase key restores the heap.

6.7.3 Extract Max and Insert

Extract max: in a max heap the root holds the maximum element — that is the catch, and it is why extraction is a operation: take the root, and heapify. Every one of these methods is the heapification process in another disguise, so every one runs in :

  • Extract max — remove the root (the maximum), then Max-Heapify: .
  • Max heap insert — the insertion procedure from before: place the new element at the next available location and bubble upward: .
  • Increase key — raise a stored key and bubble upward: .

And a final reminder from the session: Build-Max-Heap can be done in either or linear time — always do the linear version.

6.7.4 Assumptions and Scope

Assumption: Increase key assumes the new key is greater than the current value at the node. Raising a key is an upward-only repair; if a key ever decreases, the heap property can break below the node instead, and the repair must be a downward bubble (heapify) rather than an upheap-style climb. Scope: The navigation rules assume 1-based indexing of the vector, as used throughout the course; with 0-based indexing the formulas become , , . Extract max and insert assume the heap property already holds before the operation — they restore it, they do not build the heap from scratch.

6.7.5 Student Questions and Answers

Q: Can increase key be applied to any node? A: Yes — to any node. But if the node is internal, you first need to find that node — its index — before you can increase its value. Once you are at the node, the exchange-with-parent procedure is the same: compare with the parent and keep exchanging while the parent is smaller.

6.7.6 Exam Notes

Exam note: the practice exercise given in the session: "After you construct this heap, show what happens when you increase the key 5 from 5 to 33, then do heapsort and explain why the runtime of this algorithm is " — expect this exact shape of question: construct, increase key, sort, and justify the bound. The navigation rules (parent , left child , right child ) are constant-time recall items.

Recap + Bridge: the helper operations — navigation, increase key, extract max, insert — are all the same heapification machinery in disguise, each . That completes the heap toolkit: with these helpers plus the linear build, the heap is ready for every consumer studied so far. The session now turns from trees to a new search structure: first binary search as the baseline, then the dictionary, which promises something better on average.

Real-world connection: increase key is the operation behind priority upgrades in live systems — a network packet that receives a "pay more" tag in a metered queue, a job whose urgency is raised by an operator, or a Dijkstra-style shortest-path engine (later in the course) where a tentative distance that improves must be promoted inside the heap. In each case the data structure must react to a rising key in logarithmic time, not a full rebuild.

6.8 Binary Search and the Case for Dictionaries

6.8.1 Binary Search on a Sorted List

Given a sorted list of numbers, what is the best search algorithm? Binary search: it works by divide and conquer — it halves the candidate range at every step — and runs in . That is the standard answer, and it is still far from . If you could find an algorithm that searches an element in time from a list of elements, with large, you would be the king — you would rule the world. That remark connects to the complexity classes NP-hard and NP-complete, which come up near the end of the course: an -or-even-sublinear search for arbitrary data would be the kind of breakthrough that reshapes what we think is computable.

6.8.2 Why a Dictionary

Binary search is good, so why do we study dictionaries? Because we want an algorithm that does better than binary search. With a dictionary, the search for an element is — but understand that this is the expected time, the average time. Nobody states it explicitly, but it is an average; sometimes the search enters itself, with a dictionary too. On average , worst case worse — that is the honest summary we build toward. The professor's garbled aside about "alpha" points exactly where the refinement lives: next session the expected-time analysis will be stated precisely in terms of the load factor, the ratio of stored items to table cells, and that is where the "on average" qualifier gets its real shape. For now, the claim stands in its honest form: expected , worst case worse.

Picture a sorted list as a long measuring tape with the target value hidden somewhere along it. At every step of binary search you look at the middle mark: if the tape's value there is smaller than the target, the target must lie in the right half, so you tear off the left half and discard it; if larger, you discard the right half. Each step halves the remaining tape, so the number of steps is the number of times you can halve before one mark is left — . The landmark is the middle mark itself: it is the only point of the tape you ever need to inspect. The takeaway: binary search trades sorted order for a guarantee that every comparison discards half the possibilities — excellent, but each search still has to be told where to look one element at a time, which is the gap a dictionary attacks.

6.8.4 Student Questions and Answers

Q: What is the best searching algorithm for a sorted list? A: Binary search — — it works by divide and conquer. It is the best known for a plain sorted list; the dictionary beats it only in the expected, average sense.

6.8.5 Exam Notes

Exam note: binary search on a sorted list is by divide and conquer — the baseline that motivates the dictionary. Be ready to state the dictionary claim with the professor's exact care: the figure is an average, expected time; the worst case can reach . Nobody who says "dictionary search is " flatly, without the expected-time qualifier, gets full credit.

Recap + Bridge: binary search is the best a sorted list can offer — logarithmic, not constant. The dictionary is the next ADT, and its whole selling point is that searching an item by key is constant time on average. The next section defines the dictionary and its operations.

Real-world connection: the professor closed the search discussion by pointing at Google's PageRank — the search-ranking algorithm by Larry Page, published as a paper by Google's founders. PageRank is a graph-based algorithm: it ranks web pages by the structure of links between them rather than by scanning lists, which is why the professor placed it "in alignment with the graph data structure we will be studying very soon". Dictionaries, meanwhile, are the everyday engine of lookups in compilers (symbol tables), operating systems (environment registries), and every language's built-in map.

6.9 The Dictionary ADT

6.9.1 Definition and Main Operations

A dictionary stores key–element pairs, which we call items. is the key and is the element. The dictionary models a searchable collection of items: the main operations are inserting, searching, and deleting. An item is stored with its key, and a search looks up the element by key. A key is an identifier that is assigned by an application or a user to an associated element — in a dictionary of student records, for instance, the key might be the student's ID number and the element the student's full record. The benefit we are chasing: insert, search, and delete in time — but whether it is exactly or something more is what the coming sections decide. To be precise from the start: findElement is on average but can reach in the worst case.

The methods: findElement returns the element of the item with key ; if the dictionary has no item with that key, it returns a special element called noSuchKey — a sentinel, a marker value that signals "not found" and can never be confused with a real element. insertItem inserts an item with element and key . removeElement removes the item with key . Then the generic methods size and isEmpty, and the iterator methods keys() and elements() — keys() walks through all the keys of the dictionary, elements() through all the elements. Because duplicate keys are allowed (next subsection), the dictionary can also offer findAllElements, which returns an iterator over all elements whose key equals , and removeAllElements, which removes them all at once.

6.9.2 Duplicate Items and Collisions

Is it possible to store two entries with one key in a dictionary? The instinctive answer is no — a key should be unique. But the answer is yes: it is allowed. When multiple entries share the same key, you get a collision, and we will study how to handle — not eliminate — the collision: a collision is a collision; once it happens we can handle it, and we can reduce future ones, with strategies we will see with hash tables. Collisions are the price of duplicate keys, and choosing a good key is the first line of defense.

6.9.3 Choosing a Good Key

The lesson of the duplicates discussion: choose keys with as few duplicates as possible. A bad key choice — hashing the category "fruits" to values like apple, banana, orange — guarantees collisions; do not choose a key like that. The same holds for first names: many people share a first name — Bob Smith and Bob John produce the identical hash value — so a first name is a poor key. For student records, the natural key is the student's ID number, and we would probably want to disallow two students sharing one ID number. Choose the key smartly, so that it has as few duplicates as possible.

6.9.4 Examples

Real-world: the classic dictionary example maps countries to their currency — the currency of Greece is euro. Dictionaries use an array-like syntax for indexing: dictionary[Greece] returns euro. Unlike a standard array, the indices of a dictionary need not be consecutive, and they need not even be numeric — here the keys are strings, the country names. That is the whole point of the array-like syntax: the "index" is whatever key the application chose, and the lookup returns the element attached to that key.

Real-world: counting the number of occurrences of words in a document — the word counting example — a dictionary is ideal: use the words as keys and the word counts as values.

Worked example. Suppose the document is a single line: "the cat sat on the mat". We count occurrences with a dictionary whose keys are words and whose values are counts.

Step 1 — Read the first word, "the". The dictionary is empty, so "the" is not present. Insert the item ("the", 1).

Step 2 — Read "cat", "sat", "on", "mat". None of these has been seen, so each is inserted with count 1: ("cat", 1), ("sat", 1), ("on", 1), ("mat", 1).

Step 3 — Read the second "the". The key "the" is already present with count 1, so instead of inserting, we update: the count becomes 2.

Final dictionary: the → 2, cat → 1, sat → 1, on → 1, mat → 1. Sense-check. The document contains the word "the" twice and every other word once — the counts add to , the total number of words, so the tally is consistent.

Once the dictionary topic is complete, this is easy to implement; for this one you can use the inbuilt Python dictionary class directly, rather than building one from scratch.

6.9.5 Assumptions and Scope

Assumption: The ADT assumes keys can be compared for equality (to find the item with a given key) and, for the ordered variants, totally ordered. Keys need not be unique — the definition allows multiple items with the same key, and applications that require uniqueness (student IDs) enforce it at the application level, not inside the ADT. Scope: The dictionary makes no ordering promise: it is a searchable collection, not a sorted one. An ordered dictionary that keeps keys sorted is a separate ADT studied with search trees. The benefit is an expected-time claim that the hash-table sections make precise; until then, the honest worst case is .

6.9.6 Common Pitfalls

  • Believing duplicate keys are forbidden. Most of the class answered "no" to the duplicate-key question — the professor said that instinct is wrong: duplicate keys are allowed, and collisions are handled, not eliminated.
  • Choosing a key full of duplicates. First names, categories like "fruits", or any key shared by many entries guarantees collisions. The key should be as close to unique as the application allows.
  • Confusing the key with the element. The key is the searchable identifier; the element is the data carried. Searching by key never searches the element, and the two are stored as a pair.
  • Treating the dictionary as ordered. A dictionary answers "is this key present, and what is attached to it?" — it does not answer "what is the next larger key?". Ordered lookups are the job of an ordered dictionary (binary search trees, later in the course).

6.9.7 Student Questions and Answers

Q: Are multiple entries with the same key allowed in a dictionary? A: Yes — it is allowed. There will be a collision: multiple entries mapping to the same place. We will not eliminate collisions — they are handled, and we can try to reduce them, with the strategies we study when we reach hash tables. Most of the class said no; that is the common intuition, but it is wrong. This is also why choosing the key smartly matters — duplicate keys, not the hash function alone, are what create collisions.

6.9.8 Exam Notes

Exam note: dictionary search is an average, expected-time claim; the worst case is — say this carefully in answers. Also recall the sentinel: a failed findElement or removeElement returns noSuchKey, a special marker that must not be mistaken for a stored element. The duplicate-key answer (allowed, collisions handled) is a correction the professor expects students to remember.

Recap + Bridge: the dictionary is the searchable key–element container with the -on-average promise; duplicate keys are allowed and collisions are handled. The first implementation shows both ideas in their simplest form: the log file, which trades search speed for trivial insertion.

Real-world connection: dictionaries are the lookup engine of nearly every program — the compiler's symbol table maps identifiers to types and addresses, the operating system maps environment variable names to their values, and every web application maps user IDs to session data. The professor's country-to-currency and word-counting examples are miniature versions of these; the word-counting one is directly implementable with Python's dictionary class, which is itself a real, tuned hash-table dictionary.

6.10 The Log File Implementation

6.10.1 Definition and Costs

Hook: A dictionary that is written to constantly but searched almost never — like a company's login log — does not need fancy machinery. The log file is the simplest dictionary there is, and for exactly those workloads it is the right one.

A log file is a dictionary implemented with an unsorted sequence: the items of the dictionary are stored in the sequence in arbitrary order — whichever order they happened to arrive in. This is also called the unordered-sequence implementation of the dictionary ADT, and its three costs are easy to derive.

The space required is — the structure keeps its memory usage proportional to its size. Insertion takes , because there is no order to maintain; we simply append. findElement and removeElement for a key take , because the sequence must be scanned: to find an item with a given key we walk the sequence from the start, comparing keys one by one, until the key is found or the sequence ends.

6.10.2 When a Log File Makes Sense

Real-world: a log file is exactly what companies use for login records. If you are logging the punch-in and punch-out details of employees — login records with timestamps — you seldom search them: only when there is a dispute, when an employee claims a login time different from the one registered, does anyone search the log file. The file is searched rarely — searches and removals are very rare in such a file — so the scan, paid once in a while, is perfectly manageable.

As each employee punches in, the details are inserted one after another, with no ordering by employee name or employee number. The log grows by appending: employee 5 punches in at 9:02, employee 12 at 9:04, employee 5 again at 9:05 — each record lands at the end, in arrival order. There is no index, no sort, no structure to maintain, and that is precisely why insertion stays .

The log file is effective only for small dictionaries, or for dictionaries on which insertions are the most common operations — whenever searches, deletions, or accesses dominate, we need a better implementation, and that is where the hash table enters.

6.10.3 Visual Intuition — The Append-Only Ledger

Picture a paper ledger with one line per event, written in chronological order — an audit trail. Every punch-in is a new line at the bottom; nobody reorders the lines and nobody deletes them. Finding a particular employee's record means reading the ledger from the top until the matching line appears — the whole book, in the worst case. The landmark is the bottom of the ledger: that is where every new record lands, in constant time, which is why the ledger is so cheap to keep. The takeaway: a log file is a dictionary that optimizes the write side and accepts an expensive read side, because its real-world workloads read rarely.

6.10.4 Assumptions and Scope

Assumption: The log file works on the assumption that inserts dominate — each insert is , so a workload of inserts with only occasional lookups stays cheap overall. It also assumes the sequence grows by appending, so both a vector and a linked list give the same insert at the tail. Scope: The moment searches or removals become frequent, the scan per lookup dominates and the structure degrades — the professor's rule: effective only for small dictionaries, or dictionaries where insertions are the most common operation. The space is proportional to the number of items actually stored, which is why the structure never wastes memory.

6.10.5 Student Questions and Answers

Q: Can we use a log file sorted by date instead of an unsorted one? A: That is entirely up to you — it depends on the application and the requirement. If employees keep arriving and you keep inserting, an unsorted array is simpler: as each employee punches in, the record is appended, with no ordering by name or number. Sort only if your searches actually need it — sorting costs on every insert, so it only pays when searches happen often enough to justify it.

6.10.6 Exam Notes

Exam note: the log file's cost table is a recall item — space , insert , findElement and removeElement — and the design rule is examinable: use a log file when insertions dominate and searches are rare (the punch-in/punch-out example); move to a hash table when searches, deletions, or accesses dominate.

Recap + Bridge: the log file implements the dictionary on an unsorted sequence, paying for cheap insertion with linear searches. That trade is acceptable for append-heavy, search-rare workloads — but the dictionary's -on-average promise needs a better structure. The next section builds it: bucket arrays and hash functions.

Real-world connection: the log-file pattern is everywhere in industry under the name append-only log or audit trail: system event logs, database write-ahead logs, and version-control histories all share the same shape — write constantly, read rarely, and when a read happens, scan or replay. The professor's login-records example is the same pattern in miniature: companies keep the punch-in and punch-out details precisely because they are cheap to write, and the search cost is paid only in disputes.

6.11 Bucket Arrays and Hashing

6.11.1 Bucket Array and Hash Function

Hook: The dictionary promised constant-time lookups, but the log file could only scan. The fix is to stop searching altogether: compute a single number from the key, and walk straight to the one place where the item must be. That number is produced by a hash function, and the array it indexes is a bucket array.

A dictionary that must answer searches fast is built with hashing. The data structure: a bucket array of size , together with a hash function . The goal: for a key–element pair , the bucket array stores the pair at location — not at , not at , at the location the hash function computes from the key. The array is called the bucket array because each location is a bucket: in the ideal case every bucket holds exactly one item; when a bucket must hold several, that is where collisions begin. Together, the bucket array and the hash function form the hash table — the structure a dictionary implementation is built on.

A bucket array needs keys that are unique integers in the range to — and that requirement has two built-in challenges. First, the keys we actually have may not be integers at all. Second, even integer keys may span a range far larger than the table — keys in the range to some huge number, with only 100 locations available. The hash function solves both: it converts any key to an integer, and it brings that integer into the range to .

6.11.2 Hash Values with Integer Keys

For integer keys, the simplest hash function is the modulo function: . The value is called the hash value of the key . Example: with , the key 10 hashes to 4. Keys with hash value 6 all land in location 6 — and that is the beginning of collisions: for whichever element the hash value is 6, all those elements are stored in the same bucket. If keys are not unique, two different elements may be mapped to the same bucket, and that is when we say a collision has occurred.

Worked example. Consider a bucket array of capacity eleven (11) — the locations run from 0 to — with the items 1D, 25C, 3F, 14C, 6A, 39C, hashed with the hash function :

  • Item 1D: , stored at location 1.
  • Item 25C: (22 and 3), stored at location 3.
  • Item 3F: — a second item at location 3: a collision.
  • Item 14C: — a third item at location 3.
  • Item 6A: , stored at location 6.
  • Item 39C: — a second item at location 6.

Because 11 is the size , the locations run from to . The items 3F and 14C were pushed into location 3 alongside 25C, and 39C into location 6 alongside 6A — three items in one bucket, two in another, and the rest of the buckets empty. Sense-check. Every result is the remainder after dividing by 11, so every location lies between 0 and 10, and keys that differ by a multiple of 11 (3, 14, 25) all land on the same remainder — which is exactly what a collision is.

A hash function is good if it maps the keys of our dictionary so as to minimize the collisions as much as possible. If a key is not an integer, other methods apply: for a string you can use the number of letters, the ASCII characters of the string, or a polynomial conversion; the standard polynomial method multiplies each ASCII character by a positional factor. These are standard conversion techniques worth exploring on your own.

6.11.3 Two Jobs of a Hash Function

A hash function has two jobs, with two names. Hash code mapping: convert the key — whatever its type — into an integer. Second, compression mapping: bring that integer into the range to , usually by taking it modulo . The order matters: hash code mapping is applied first, and then compression mapping. Compression mapping is needed even when the keys are already integers, if they lie in a larger range than the table size.

For a dictionary storing social security numbers (nine-digit positive integers) with names, a simple hash function is the last four digits of the number: . The social security number 000000001 hashes to 1, so it is stored at location 1. The modular form and the "last four digits" rule agree exactly: the remainder after dividing by 10000 is the number's last four digits, so an SSN ending in 0001 always lands at location 1. Python's built-in hash function shows the same two-stage idea — try it out: for a string it applies the polynomial mapping to the characters.

6.11.4 Visual Intuition — The Funnel and the Boxes

Picture a row of boxes labeled 0 through — the bucket array. Above them hangs a funnel with a dial: the hash function. Every key is dropped into the funnel, the dial spins, and the key falls into the box whose label the dial printed. A good hash function spreads the falling keys across all the boxes; a bad one dumps many keys into the same box. The landmark to watch is a box holding more than one key — a collision — because that is the moment handling machinery is needed. The takeaway: hashing turns "find where this key lives" into "compute one number and walk to that box" — the whole speed of the dictionary rests on that single computation.

6.11.5 Common Pitfalls

  • Forgetting the two stages. The hash function converts the key to an integer first (hash code mapping), then squeezes that integer into (compression mapping). Students who compress before converting apply modulo to a key that is not a number yet.
  • Using the key itself as the index. A bucket array index must be an integer in ; keys that are strings, or integers far outside that range, must pass through both mappings.
  • Believing a hash function can eliminate collisions. It can only minimize them; the professor's point stands — collisions are unavoidable, because incoming keys cannot be predicted.
  • Treating the hash value as the key. is a location, not an identifier; several distinct keys share a location, and only comparing the keys themselves distinguishes them (Section 6.13).

6.11.6 Student Questions and Answers

Q: The purpose of a dictionary and the purpose of a hash table seem the same. Why two different implementations? A: The dictionary is the ADT — the abstract idea of key–element pairs with insert, search, and delete. The hash table is one implementation of that ADT. Inside the implementation, a hash function has two parts: hash code mapping converts the key to an integer, and compression mapping brings that integer into the range to — needed when the keys are already integers but span a larger range than the table.

Q: What can we do if the keys are not integers, or if the integer keys range too large for our 100 slots? A: Those are exactly the two challenges the hash function solves. Bucket arrays need unique integers in the range to . If the keys are non-integers, hash code mapping converts them to integers; if the integers are in a larger range, compression mapping brings them into to . For a string key, count the letters, sum the ASCII characters, or use the polynomial method.

6.11.7 Exam Notes

Exam note: the two-job definition is a favorite question — hash code mapping first (key to integer), compression mapping second (integer into ), and compression is needed even for already-integer keys when they exceed the table size. Also expect the worked pattern of the capacity-eleven example: compute , place the item, and say "collision" out loud whenever a bucket receives its second item.

Recap + Bridge: the hash table is the dictionary's fast implementation — a bucket array plus a two-stage hash function that maps any key to one of locations, with collisions acknowledged from the start. The next question is a design detail that changes how often collisions happen: how big should the table be, and why must the size be prime?

Real-world connection: every serious language runtime is a hash-table shop — Python's dictionary and hash() built-in, Java's HashMap, and C++'s unordered_map all apply hash code mapping (Python uses the polynomial mapping for strings) and then a compression step into the table's range. Compiler symbol tables and environment-variable registries are the same structure: names as keys, properties as elements, and a hash function deciding where each name lives.

6.12 Table Size and Prime Numbers

6.12.1 Why the Size Should Be Prime

The size of the hash table is usually chosen to be a prime number — any real-world implementation you will see does this. The full reason is a number-theory proof, beyond the scope of the course, but the intuition is simple, because the bias is easy to see. Suppose the table size is even, and the keys we insert are also even. Then every computed index is even: the table develops a bias — only the even locations fill, only half of the slots are used, and the odd indices never get used at all. The reverse holds too: an odd size can leave the even indices unused, depending on the keys.

The modular arithmetic behind it: an even size and an even key always give an even remainder, because an even number minus any multiple of an even number is still even. So with even, half the table — all odd locations — can never be reached by even keys. A prime size spreads the indices evenly, so collisions drop. If you want the full mathematical argument, it is a long proof that you can look up — the professor's advice was to skip it unless you are a maths expert, because the conceptual reason is the part that matters.

6.12.2 A Worked Example with an Even Size

Worked example: after hash code mapping, suppose the keys are 200, 205, 210, 215, 220, and so on, in steps of 5, up to 600 — and the bucket size is 100, an even number.

Worked example. The keys run from 200 to 600 in steps of 5: 200, 205, 210, 215, 220, ..., 600 — 81 keys in total. Hash them with .

Step 1 — Compute the first few locations. , , , , and so on — every remainder is a multiple of 5.

Step 2 — Follow the pattern to the end. Location 0 collects 200, 300, 400, 500, and 600 — five items. Location 5 collects 205, 305, 405, and 505 — four items. Every one of the 20 reachable locations (the multiples of 5 between 0 and 95) collects either four or five items: 200, 300, 400, 500, 600 all land in location 0, while 205, 305, 405, and 505 all land in location 5.

Step 3 — Read the damage. Even keys collide here: four or five items gather in every bucket — four or five collisions per bucket. The remaining 80 locations are never touched. With the same keys and , a prime, there is no collision at all — that is why the table size is chosen prime.

Sense-check. With , the pattern repeats every 100 units, so keys that differ by 100 must land together; with , the keys advance by 5 in a cycle of length 101, and 81 keys are too few to repeat any location — the residues 99, 3, 8, 13, ... are all distinct, since 5 and 101 share no common factor.

6.12.3 Visual Intuition — The Half-Empty Table

Picture a bar chart of the 100 buckets of the example, with bucket index 0 to 99 on the horizontal axis and the number of stored items on the vertical axis. The filled bars stand only over the multiples of 5 — 0, 5, 10, ..., 95 — each four or five units tall; the other 80 positions are empty ground. The landmark is the empty stretch: an entire half of the table that even keys can never reach. With , the same 81 keys produce 81 separate single-item bars with no empty pattern at all. The takeaway: a non-prime size lets the keys' own structure repeat inside the table, and repetition is what turns into collisions.

6.12.4 Common Pitfalls

  • Choosing an even table size. With even keys, an even keeps every index even — half the slots never fill, and collisions multiply. The same bias appears for any size that shares a factor with the key pattern.
  • Blaming the hash function for what the size caused. The keys 200 to 600 in steps of 5 are perfectly fine keys; the disaster came from sharing the factor 5. The compression side of the hash function is responsible for this bias too.
  • Believing a prime size eliminates all collisions. It removes the structured bias, not collisions in general — repeated patterns of the form can still collide, and a good hash function remains necessary.
  • Memorizing the number-theory proof. The professor said it is beyond the course scope and will not be examined — the conceptual bias argument is what the exam tests.

6.12.5 Student Questions and Answers

Q: Why is the size of a hash table chosen to be a prime number? A: Conceptually, because of the modulo bias: with an even table size and even keys, the hash values are all even, so only half the slots get used — and that ends in collisions. The mathematical proof belongs to number theory and is beyond the course scope; you can look it up if you want the full argument.

Q: What is 200 mod 101 and why is it not 200? A: It equals 99 because one full 101 fits into 200, leaving 99 as the remainder; only multiples of 101 — 101, 202, 303 — give zero. 200 is not a multiple of 101, so 200 cannot be its own remainder. With the prime size 101, none of the keys 200 through 600 is a multiple of 101, so every key lands in its own bucket.

Exam note: the examinable takeaway is the bias argument — even size plus even keys uses only even indices, so only half the slots fill, which ends in collisions — and the worked pattern of the 200-to-600 example: gives four or five collisions per bucket, gives none. The number-theory proof itself is out of scope.

Recap + Bridge: the table size is chosen prime so that structured keys cannot bias the table into half its slots. The table and the function are now in place — but as the capacity-eleven example showed, collisions still happen. The final topic of the session is how real systems deal with them: handling collisions.

Real-world connection: real hash tables nearly always size their tables at prime numbers — Java's HashMap uses prime table sizes (with power-of-two sizes chosen only after randomized hash spreading), and Python grows its dictionaries through a sequence of prime-capacity sizes. The same modulo-bias reasoning also explains why databases and caches pick prime-sized hash buckets when the data keys are known to follow patterns such as multiples or arithmetic progressions.

6.13 Handling Collisions

6.13.1 Why Collisions Happen

Collisions occur when different elements are mapped to the same cell. Can collisions be avoided? Not really. However good the hash function, we cannot predict the incoming keys — the keys that will arrive tomorrow, or the day after tomorrow — so sooner or later two keys land in one cell. Collisions are unavoidable, and the effort spent hunting for a perfect hash function is not worth it compared with handling them: most real-world systems simply handle collisions. The best hash function in the world is useless against keys you cannot foresee. So the question is not "how do we prevent collisions" but "what do we do when one happens" — and the very first method, the subject of the rest of this section, is very direct: separate chaining.

6.13.2 Separate Chaining

The first collision handling method is very direct: separate chaining. Let each cell in the table point to a linked list of the elements that map there. Each bucket holds that linked list: the array location stores a pointer to the head of the list, and every item whose key hashes to that cell is appended to it. Instead of one array, we have a bucket array where each location is the head of its own list. In the ideal case — a good hash function, few collisions — most buckets are empty or hold a single item, and the lists are tiny. When a bucket must hold several, the list simply grows: the hash table itself never gets filled up, because wherever a collision occurs, the linked list grows.

6.13.3 Algorithms

The algorithms are straightforward, and each delegates the work to the list sitting in the bucket. Let — the list stored at the bucket for key .

  • findElement: if is empty, return the sentinel noSuchKey; otherwise search for the key inside the sequence , walking the linked list from the head, and return the element found.
  • insertItem: if is empty, create a new, initially empty, sequence-based dictionary — the linked list — and attach it to the bucket; otherwise take the existing list; then insert the item with key and element into that list.
  • removeElement: get the list at ; if it is empty, return noSuchKey; otherwise remove the item with the key.

Separate chaining sets equal to the list at and searches it; in the worst case — a bucket whose list holds many items — findElement takes . The hash function's spreading power is what keeps the lists short: with a good function most buckets hold zero or one item, and each operation stays near constant time.

6.13.4 A Worked Example

Worked example. Consider a table of size 7 with the hash function .

Step 1 — Insert the first key. The first key is 23: (21 and 2), so 23 is inserted at bucket 2. There is no need for separate chaining at all here, because no collision occurred — the bucket was empty and the item sits directly in it.

Step 2 — Insert a colliding key. Separate chaining matters only when a second key hashes to a bucket that is already occupied; only then does a list form. Suppose a later key such as 16 arrives: , and 16 joins 23 in the same bucket.

Step 3 — Read the result. Bucket 2 now holds a two-item list, and any search for either key must walk that list — the chain that separate chaining exists to grow.

Sense-check. No collision occurred for the first key, so no list was built; the list appeared only when the second key arrived at the same bucket. The search cost at bucket 2 is now the length of its chain, which is why the whole design depends on keeping chains short.

6.13.5 Pros and Cons

Separate chaining is simple, direct, and easy to implement — that is its great strength. It also never fills up: the hash table itself never becomes full, because wherever a collision occurs, the linked list simply grows. The weakness is space and access speed. If too many elements map to one location, the list at that bucket grows toward items, and searching an -sized linked list costs — read "big-O of n" — at that point the advantage of the dictionary is gone, and the whole bucket-array-and-lists machinery is worse than just using a plain array or a plain linked list. If many collisions occur, every advantage for which the dictionary was chosen is invalidated, and the time complexity climbs.

6.13.6 Visual Intuition — Chains Hanging from Buckets

Picture the bucket array as a row of hooks, labeled 0 through , with chains hanging from some of them. A key arrives, the hash function points at a hook, and the item is clipped onto the chain there — behind any items already hanging. Most hooks hold nothing or one item; the chain hangs from a hook only where collisions have happened. The landmark is the longest chain: its length is the worst case for any search, because finding an item at that bucket means walking the whole chain. The takeaway: separate chaining converts "table full" into "chains grow", and the price of that flexibility is paid wherever a chain grows long.

6.13.7 Common Pitfalls

  • Saying dictionary search is flatly. The claim holds only under the smart assumption of a really good hash function, unique keys, and collisions as rare as possible; the honest worst case is — see the third Q&A below.
  • Searching for the hash value instead of the key. Inside the bucket's list, the items carry different keys that happen to share a location; only comparing the keys themselves finds the right item.
  • Forgetting the sentinel. An empty bucket must answer noSuchKey, not a guess; the sentinel is what makes "not found" distinguishable from a real element.
  • Letting one bucket grow unchecked. The whole design assumes the hash function keeps buckets small; when a single list approaches items, the structure degenerates to a linked list and all dictionary advantages vanish.

6.13.8 Student Questions and Answers

Q: After we get the linked list at the location where the key hashes to, do we search it? A: Yes — that is exactly the cost we account for. Suppose the key hashes to the value 4. We come to location 4 and get its list. Then we search for the key itself inside that list — not for the hash value . The keys in the list are different keys that happen to share the same hash value; only the search distinguishes them.

Q: What about the time required for hashing itself? A: We consider the hashing time constant — we do not include it in the analysis. The hash function is a computation we accept as a constant-time step.

Q: What is the worst-case time of findElement in a dictionary? A: In the worst case it is . Do not close your eyes and say the time is : holds only under the smart assumption that the hash function is really good, the keys are unique, and collisions are as rare as possible. The claim is an average, expected-time claim — and further refinements, using the load factor, come when we analyze the dictionary in more depth.

6.13.9 Exam Notes

Exam note: separate chaining is the first of the collision handling methods, and it is the one covered in this session; the remaining two methods come in the next session. The professor's closing warning is examinable in the same spirit as "why 14 and not 8": never quote dictionary search as flat — say the worst case is and the is the average, expected time. Mid-sem syllabus update: the syllabus currently runs through binary search tree — everything studied so far, plus BST and dictionary. The dictionary topic is completed next session, binary search tree starts Monday, and there is an extra session on Monday 7 to 9 PM; a sample paper will be discussed in that session rather than uploaded.

Recap + Bridge: collisions are unavoidable, and separate chaining handles them by turning each bucket into a linked list — simple, never full, but vulnerable to long chains. The session closes here: the dictionary is half covered, and the next session finishes it (the remaining collision methods and the load-factor analysis) before binary search trees begin on Monday.

Real-world connection: separate chaining is the collision strategy inside some of the most-used hash tables in industry — Java's HashMap chains its buckets, and DNS caches and database hash indexes grow chains when keys collide. The professor's design rule transfers directly: real systems accept collisions as a fact of life, spend their effort on handling them, and rely on the hash function plus a good table size (prime, as in Section 6.12) to keep the chains short.

Exam Guidance Summary

  • Mid-sem syllabus: everything studied so far, plus binary search tree and dictionary — the syllabus ends at binary search tree. The dictionary topic is completed in the next session; binary search tree starts Monday, with an extra session announced in advance (Monday 7 to 9 PM).
  • Sample paper: it will be discussed in Monday's session, not uploaded — if the pattern did not match exactly, there would be complaints.
  • Dictionary: half covered in this session (definition, log file, hash tables, separate chaining); the rest, including the two remaining collision handling methods, comes in the next session.
  • Building a heap: the answer is , linear time, with the loop from down to 1. The mathematical proof of the bound is not asked in the exam — with a search engine the proof would simply be copied out.
  • Heapify: expect conceptual questions like "why is 14 exchanged and not 8" — the max-heap rule: compare the children, take the maximum, and exchange with that one.
  • Priority queue sorting: always phrase the output as non-decreasing order, to accommodate equal keys.
  • Heapsort: practice question of the session — "after you construct this heap, show what happens when you increase the key 5 from 5 to 33, then do heapsort and explain why the runtime of this algorithm is ". Heapsort returns in the sorting unit later in the course — "keep this in mind".
  • Recall questions (insertion steps, removal steps, upheap and downheap complexities) should be answered instantly — there is nothing to think about if you have understood.
  • Dictionary search: the claim is an average, expected-time claim; the worst case is — say this carefully in answers, and never "close your eyes and say ".
  • The versus difference matters at large scale: make the balanced implementation choice with that in mind — when is large, and make a difference of day and night.

Key Industry Applications

  • Priority queues: standby flyers (priority by fare paid, frequent-flyer status, or check-in time); sealed-bid auctions (customers bid on arrival and gain priority over lower bids); stock markets (matching buyers and sellers, tie-breaking by earliest bid or lowest bid — the matching engine is a priority queue either way).
  • Heap increase key: auction item prices rising, stock values rising, a customer's priority rising because they decide to pay more — in each case the stored key rises and the heap is repaired in logarithmic time.
  • Log files: punch-in and punch-out login records in companies — insertion-heavy, search-rare (only disputes are searched), so the scan is paid once in a while. The same append-only pattern drives system event logs and audit trails.
  • Dictionaries: counting word occurrences in a document — words as keys, counts as values; implement directly with the Python dictionary class. Compiler symbol tables and environment-variable registries are dictionaries in disguise.
  • Hash tables: Python's built-in hash function shows hash code mapping (polynomial mapping for strings) followed by compression; social security number tables use the last-four-digits hash . Real runtimes — Python, Java, C++ — all implement the dictionary on top of hash tables with prime-sized growth.
  • Google PageRank: the search ranking algorithm by Larry Page — a graph-based algorithm, aligned with the graph data structure topic that comes next (graphs begin in a few sessions).
  • General principle: if the application needs many inserts, choose the heap; if it needs many extractions, choose the sorted sequence — when is large, and differ by day and night.

DSA Lecture 6 notes · Heaps, Priority Queues, Dictionaries, and Hash Tables

Data Structures and Algorithms· postgraduate· 2026-08-09

Sections Breakdown

16.1 Heap Operations Recap

Insertion (bubble-up) and removal (bubble-down) on a vector-based heap, the shared O(h) = O(log n) cost of both procedures, and the visual picture of the two bubbles.

26.2 Fixing a Broken Heap

The fix-down (heapify) procedure for a node whose subtrees are already heaps — compare the children, take the maximum, exchange — with a fully worked repair and its assumptions.

36.3 Constructing a Heap

Repeated insertion versus bottom-up construction: the naive O(n log n) estimate corrected to O(n) by charging each node only its own height, with the Build-Max-Heap algorithm in full.

46.4 The Priority Queue ADT

The key–element container served by insertItem and removeMin: applications, the total order on keys, and sorting by repeated removal in non-decreasing order.

56.5 Ways to Implement a Priority Queue

The three implementations — unordered sequence, sorted sequence, and heap — with their insert and removeMin costs, and the choice rule for large n.

66.6 Heapsort

Building a max heap and extracting the maximum n − 1 times to sort the array in place in O(n log n), with a ten-element worked sort and its time and space analysis.

76.7 The Helper Operations of a Heap

Parent and child navigation by index arithmetic, increase key, extract max and insert — every helper operation is heapification in disguise at O(log n).

86.8 Binary Search and the Case for Dictionaries

Binary search on a sorted list as the O(log n) divide-and-conquer baseline, and the honest dictionary claim: expected O(1), worst case worse.

96.9 The Dictionary ADT

Key–element items with findElement, insertItem and removeElement, the noSuchKey sentinel, duplicate keys and collisions, and the rules for choosing a good key.

106.10 The Log File Implementation

The dictionary on an unsorted sequence — space O(n), insert O(1), findElement and removeElement O(n) — the right choice for insertion-heavy, search-rare workloads.

116.11 Bucket Arrays and Hashing

The bucket array and the hash function, the two jobs of hashing (hash code mapping, then compression mapping), and the worked modulo-hashing example.

126.12 Table Size and Prime Numbers

Why the table size is chosen prime: the modulo bias of an even size against structured keys, demonstrated on the keys 200 to 600 in steps of 5.

136.13 Handling Collisions

Why collisions are unavoidable, and separate chaining — each bucket a linked list — with its algorithms, a worked example, and the honest worst case O(n).

14Exam Guidance Summary

The exam-relevant core of the session — build-heap in O(n), heapify questions, non-decreasing order, the heapsort practice question, and the dictionary O(1) qualifier — in one place.

15Key Industry Applications

Priority queues, increase key, log files, dictionaries, hash tables, and PageRank — where this session's structures appear in real systems.

Postgraduate students learning data structures and algorithm analysis

Exam Revision Notes

Below is the distilled, exam-ready core. Every entry comes from the full explanation above. Use this section for rapid review; return to the main notes when a point needs more context.

Heap Operations Recap

Must-know: Insertion: push into the first available leaf, then upheap. Removal: take the root (the only element ever removed), exchange with the last leaf, delete the last element, then downheap. Both cost O(h) = O(log n).

Top pitfall: Trying to remove an arbitrary element from a heap — only the root is ever removed; and forgetting that the bubble path is a single line, so the cost is the height, not the whole tree.

Self-check: A new element is placed in the smallest value of a min-heap: how far can the bubble-up travel in the worst case? (From a leaf to the root, h = O(log n) levels.)

Connects to: 6.2, 5.12, 5.13

Fixing a Broken Heap

Must-know: Heapify fixes one broken node: compare the children, exchange with the larger child (max heap), and repeat until the value reaches its proper place or a leaf. Cost: O(h) = O(log n).

Top pitfall: Exchanging with the smaller child (e.g., 8 instead of 14) — the fix then does not fix anything; and stopping after the first exchange when the value must still descend.

Self-check: Root 2 with children 14 and 8, and 14 has children 6 and 4: how many exchanges until the tree is a valid max heap? (Two: 2 swaps with 14, then with 6.)

Connects to: 6.1, 6.3

Constructing a Heap

Must-know: Building a heap is O(n), linear time — never O(n log n). The loop runs from floor(n/2) down to 1; the mathematical proof is not asked in the exam, but the exchange-versus-height pattern is fair game.

Top pitfall: Quoting O(n log n) for build-heap (the naive analysis the class made), or writing the loop as n/2 - 1 instead of n/2 down to 1.

Self-check: In the ten-element example 4, 1, 3, 2, 16, 9, 10, 14, 8, 7, where does processing start, and what is the final heap? (At index 5, holding 16; final heap 16, 14, 10, 8, 7, 9, 3, 2, 4, 1.)

Connects to: 6.2, 6.6

The Priority Queue ADT

Must-know: Priority queue: insertItem(k, o) and removeMin are the main methods; sorting with a priority queue always produces non-decreasing order because multiple elements can have the same key.

Top pitfall: Saying 'increasing order' instead of 'non-decreasing order' — equal keys make increasing order false; and confusing removeMin (removes) with minKey (only reports).

Self-check: Items keyed 5, 2, 7, 2, 9 are inserted into a priority queue and removed with removeMin repeatedly: what comes out? (2, 2, 5, 7, 9 — non-decreasing.)

Connects to: 6.5, 5.11

Ways to Implement a Priority Queue

Must-know: Three implementations: unordered sequence (insert O(1), removeMin O(n)), sorted sequence (insert O(n), removeMin O(1)), heap (both O(log n)). More inserts -> heap; more extractions -> sorted sequence.

Top pitfall: Forgetting the last-node tracking in a heap-based queue, or believing the O(log n) versus O(n) difference is irrelevant for small and large n alike — it is day and night at scale.

Self-check: Min-heap keys 2, 5, 6, 9, 7: insert (3, feb) at the next available location and heapify upward — what do the keys read now? (2, 5, 3, 9, 7, 6.)

Connects to: 6.4, 6.6

Heapsort

Must-know: Heapsort pattern: build max heap (O(n)), then n-1 rounds of exchange-root-with-last plus Max-Heapify; runtime O(n log n); in place with O(1) extra space. The heap itself is not a sorting algorithm.

Top pitfall: Calling the heap a sorting algorithm (the heap is a data structure), or forgetting to shrink the heap before heapify so the sorted tail gets re-arranged.

Self-check: Heapsort the heap 16, 14, 10, 8, 7, 9, 3, 2, 4, 1: what is the final sorted array? (1, 2, 3, 4, 7, 8, 9, 10, 14, 16.)

Connects to: 6.3, 6.7

The Helper Operations of a Heap

Must-know: Navigation: parent floor(i/2), left 2i, right 2i+1, all O(1). Increase key: bubble the raised value upward while the parent is smaller, O(log n). Practice exercise shape: construct a heap, increase key 5 to 33, heapsort, justify O(n log n).

Top pitfall: Applying increase key without first locating the node when it is internal; or using increase key for a decreased key, which needs a downward bubble instead of an upward one.

Self-check: In the heap 18, 16, 17, 15, 11, 12, 14, 13, increase the key 11 (position 5) to 17: what is the new heap? (18, 17, 17, 15, 16, 12, 14, 13.)

Connects to: 6.1, 6.6

Binary Search and the Case for Dictionaries

Must-know: Binary search on a sorted list is O(log n) by divide and conquer; the dictionary claim is expected O(1) on average, worst case O(n) — always state the expected-time qualifier.

Top pitfall: Quoting dictionary search as flat O(1) without saying it is the average/expected time — the worst case can reach O(n).

Self-check: Why is a dictionary studied if binary search is already logarithmic? (Because the dictionary promises expected O(1) search, better than binary search on average.)

Connects to: 6.9, 6.13

The Dictionary ADT

Must-know: Dictionary operations: findElement(k) returns the element or the noSuchKey sentinel, insertItem(k, e), removeElement(k), plus size, isEmpty, keys(), elements(). Duplicate keys are allowed and cause collisions, which are handled, not eliminated; choose keys with few duplicates.

Top pitfall: Saying duplicate keys are not allowed (the class's instinct, corrected by the professor); choosing keys with many duplicates such as first names; forgetting the sentinel return for failed searches.

Self-check: Counting words of 'the cat sat on the mat' in a dictionary: what is the final count of 'the'? (2.)

Connects to: 6.8, 6.10

The Log File Implementation

Must-know: Log file = dictionary on an unsorted sequence: space O(n), insert O(1), findElement and removeElement O(n). Use it when insertions dominate and searches are rare (login/punch records); switch to a hash table when searches, deletions, or accesses dominate.

Top pitfall: Using a log file when searches dominate — every lookup is an O(n) scan; and paying for sorting on insert when searches are too rare to justify it.

Self-check: Why is an O(n) search acceptable in a company login log? (Searches happen only in disputes, i.e., very rarely; inserts dominate and stay O(1).)

Connects to: 6.9, 6.11

Bucket Arrays and Hashing

Must-know: Hash table = bucket array + hash function; item (k, e) stored at h(k). Two jobs in order: hash code mapping (key to integer), then compression mapping (integer to 0..n-1), usually k mod n. Good hash function = minimizes collisions.

Top pitfall: Applying the two stages in the wrong order, or skipping compression for integer keys whose range exceeds the table size; believing collisions can be eliminated rather than minimized.

Self-check: Bucket array of capacity 11, h(x) = x mod 11: where do 25C and 14C land, and what is the event called? (Both at location 3 — a collision.)

Connects to: 6.9, 6.12

Table Size and Prime Numbers

Must-know: Prime table size avoids modulo bias: even size + even keys keeps every index even, so only half the slots fill and collisions result; 200 mod 101 = 99. The number-theory proof is not examinable; the bias argument is.

Top pitfall: Choosing an even table size; believing a prime size eliminates all collisions (it removes structured bias only); quoting 200 mod 101 as 200 (it is 99, since one full 101 fits).

Self-check: Keys 200..600 in steps of 5 with n = 100: how many items land in location 0, and what changes with n = 101? (Five items — 200, 300, 400, 500, 600; with 101, none collide.)

Connects to: 6.11, 6.13

Handling Collisions

Must-know: Separate chaining: each bucket holds a linked list; findElement returns noSuchKey for an empty bucket else searches the list for key k itself (not h(k)); insert creates the list on first use; worst case O(n), O(1) only with a good hash, unique keys, rare collisions.

Top pitfall: Quoting dictionary search as flat O(1) — the worst case is O(n); searching for the hash value h(k) in the bucket instead of the key k itself; forgetting the noSuchKey sentinel for empty buckets.

Self-check: Table of size 7 with h(k) = k mod 7: where does key 23 go, and when does separate chaining first matter? (Bucket 2, with no collision; chaining matters only when a second key also hashes to 2.)

Connects to: 6.11, 6.12

Was this lecture useful?

Loading comments…
🤖

BitsNotes AI Assistant

Subject Notes Assistant

Configure AI Chat

Choose how to access the chatbot
Have your own API key?

Switch to "Bring Your Own Key" tab above for unlimited access with any OpenAI-compatible provider.

🔑 Enter API key above to fetch live models from provider, or enter model name manually.
OpenAI-Compatible API Support

Choose any provider preset (Gemini, DeepSeek, Kimi, GLM, MiniMax, Qwen, OpenAI, Groq, Ollama, etc.) or enter a custom endpoint URL.

Security & Privacy First

Your API key is sent directly from your browser to your specified provider. BitsNotes servers never store or see your key.