3.3.3 · D4Hashing

Exercises — Chaining — linked lists in buckets, load factor, resizing

3,176 words14 min readBack to topic

Throughout, we reuse the vocabulary of the parent note Chaining — linked lists in buckets, load factor, resizing:

  • = number of keys currently stored.
  • = number of buckets (array slots).
  • = load factor = average keys per bucket.
  • = the hash function sending a key to a slot in .

If any of those still feel fuzzy, re-read the parent before starting.


Level 1 — Recognition

L1.1 — Read off the load factor

A chained hash table has buckets and currently stores keys. What is ?

Recall Solution

. What we did: plugged into the definition. Why: is literally "keys divided by buckets," the average chain length. So the average bucket holds keys.

L1.2 — Which cost is which?

For a chained hash table, match each phrase to , , or : (a) worst-case search, (b) expected search under uniform hashing, (c) computing the hash of one key.

Recall Solution

(a) — every key could be in one chain. (b) — one bucket touch, then walk items on average. (c) — a hash is a fixed computation on the key. Why these: the "" is the hash+bucket touch; the "" is the walk; the worst case ignores the average and imagines the whole chain collapsing to one slot.

L1.3 — Does chaining die at ?

True or false: a chained hash table stops working once reaches .

Recall Solution

False. At the average chain length is ; buckets simply hold more nodes as grows. It degrades gracefully. The " hard wall" belongs to Open Addressing, where the array physically fills up.


Level 2 — Application

L2.1 — Insert a sequence, draw the chains

Table with , , prepend at head (see the convention above). Insert in order: . List the final contents of every bucket (head first).

Recall Solution

Compute each slot: , , , , .

  • Bucket 0:
  • Bucket 1: (empty)
  • Bucket 2: prepend order , then , then , then → head-first:
  • Bucket 3: (empty)
  • Bucket 4: (empty)

Read the figure to see why prepend reverses order. Follow the teal arrows in slot 2 from the head: they point , which is exactly the reverse of the insertion order . Each new key was clipped onto the head, shoving the older ones rightward — the picture makes that "shoving" visible in a way the text list cannot: you can trace the head pointer and watch the newest arrival always sitting leftmost.

Figure — Chaining — linked lists in buckets, load factor, resizing
Why prepend order reverses: each new key is pushed onto the head, so the last inserted () ends up first.

L2.2 — Expected search cost

That table has , . Under simple uniform hashing, what is the expected number of items scanned in an unsuccessful search?

Recall Solution

. Expected unsuccessful-search work is (one hash/bucket touch plus scanning item on average). Note the gap with reality: this specific table has one chain of length 4 and three empty buckets — the uniform-hashing assumption is violated because on these particular keys is not spreading them. The formula is an average over random keys, not a guarantee for adversarial input.

L2.3 — Pick to hit a target

You expect to store keys and want . What is the smallest (assume you may pick any integer)?

Recall Solution

Need . Smallest integer: . Why divide: means , so .


Level 3 — Analysis

L3.1 — When does the next resize fire?

Table starts at , doubles when an insert would push above . Starting from empty, after how many inserts does the first resize happen, and what is afterward?

Recall Solution

Resize when , i.e. . So are fine (). The 4th insert would make , → resize to . First resize: on the 4th insert; new . Why "" not "": exactly equals the threshold and is allowed; only exceeding it triggers a grow.

L3.2 — Total copy work to grow from 4 to 512

Doubling from up through a sequence of resizes, the final size is . Sum the copy work of all resizes. Compare with the naive "add-4-slots" scheme.

Recall Solution

Define every symbol first. Let be the new table size after a doubling (so ). Let be the number of keys stored immediately after the final resize — the count that filled the last table's predecessor.

When we double to size , we copy every key currently stored. We trigger the resize the instant the old array of size would exceed its threshold , so the number of keys copied is at most . To get a clean, safe upper bound we use for each event:

  • resize to : copy
  • resize to : copy
  • resize to : copy … up to resize to : copy .

Total copy work is bounded by: Now the comparison to . By the definition above, (the "" that triggered the final grow). Then , and indeed . This is the geometric-series bound . Spread over inserts, copy work per insert is amortized. See Amortized Analysis and the same trick in Dynamic Arrays.

Add-4-slots scheme (same final key count ): you resize at sizes , copying keys: That is an arithmetic series growing like , i.e. per insert — catastrophically worse than the constant we got from doubling. Why doubling wins: the copy costs form a geometric series bounded by ; adding a constant makes them an arithmetic series that grows quadratically.

L3.3 — A resize splitting a chain

Table , . Bucket 3 holds the chain (head → tail) . Insert triggers resize to with . Rehash convention: walk the old chain from head to tail and prepend each key into the new table. Show which bucket each key lands in and the head-first order of each new chain.

Recall Solution

New slots: , , , .

Traverse the old chain head → tail: . Prepend each into the new table:

step key new slot that bucket after prepend (head → tail)
1 7
2 3
3 7
4 3

Final:

  • Bucket 3:
  • Bucket 7:

The one chain of length 4 split into two chains of length 2. The figure lets you watch the split happen — trace one key across the two panels (e.g. the plum key leaves the crowded bucket 3 on the left and lands alone-then-paired in bucket 3 on the right, while jumps to bucket 7): the geometry shows the "spreading" that the text table only lists.

Figure — Chaining — linked lists in buckets, load factor, resizing
Why it splits: doubling adds one more bit to the modulus; keys that agreed on can now disagree on , spreading them out. That is exactly how resizing flattens chains.


Level 4 — Synthesis

L4.1 — Choose chaining vs open addressing

You are building a table that must support frequent deletions and might occasionally run at . Which scheme, and why in one sentence each?

Recall Solution

Chaining. (1) Deletion is a clean linked-list unlink — no "tombstone" markers needed, unlike Open Addressing where deleting mid-probe-sequence forces lazy-deletion bookkeeping. (2) It tolerates gracefully (chains just hold ~1.4 items on average), whereas open addressing cannot even exist at (more keys than slots). Why this is synthesis: you combined the deletion property (L1-level fact) with the tolerance (L1-level fact) into a design decision under two constraints.

L4.2 — Design a resize policy for a shrinking table

A table both inserts and deletes heavily, so swings up and down. Design a policy that keeps bounded and stays amortized , avoiding "thrashing" (resize up then immediately down). Give the two thresholds.

Recall Solution

Use an asymmetric hysteresis pair:

  • Grow (double ) when would exceed .
  • Shrink (halve ) only when drops below not .

Why the gap matters: after growing, halves (to ~); after shrinking, doubles. If the shrink threshold were just below the post-grow value, a single delete could trigger a shrink, and one insert re-trigger a grow → per operation forever (thrashing). A wide dead-zone ( to ) means you must do operations between resizes, restoring amortized . Same reasoning as growable-and-shrinkable Dynamic Arrays. Why synthesis: you merged the doubling argument with a new shrink case and reasoned about the interaction between the two thresholds.


Level 5 — Mastery

L5.1 — Prove the successful-search cost from scratch

Under simple uniform hashing, derive the expected number of elements examined in a successful search, and show it is .

Recall Solution

Number the keys in the order they were inserted (each prepended to its bucket's head). To search for , we examine itself plus every key inserted after that landed in the same bucket (they sit ahead of in the list).

Define an indicator if , else , for . Under uniform hashing . The number of elements examined for is .

Averaging over a uniformly random target : Split the sum: , and So Now substitute to see the load factor appear. Split the fraction: . The first piece is directly. For the second, multiply top and bottom by : . Therefore which is . It matches the parent note's stated formula exactly. Why this tool (indicators): turning "same bucket?" into a variable lets linearity of expectation sum the collisions without tracking the messy joint distribution — the standard weapon for expected-cost proofs.

L5.2 — Collisions are unavoidable (pigeonhole)

Prove: if keys can occur, some bucket must be forced to hold keys for some input set.

Recall Solution

Put distinct keys into buckets via . By the Pigeonhole Principle, since there are more keys () than buckets (), at least one bucket receives keys. Because guarantees distinct keys exist, no hash function can avoid this. Hence collisions are structurally unavoidable — chaining exists to handle them, not prevent them. Why pigeonhole: it is the minimal counting argument that forces a repeat, requiring nothing about how works — so it applies to every hash function.

L5.3 — Full-table capstone

Build a chained table with , , prepend at head, (double when an insert would exceed it). Insert in order, resizing as needed. During a resize, traverse the old chain head → tail and prepend each key into the new table. Give the final and every bucket's contents (head-first).

Recall Solution
  • Insert : . , OK. Bucket 0: .
  • Insert : , prepend. , OK. Bucket 0: .
  • Insert : , prepend. , OK (equals threshold, allowed). Bucket 0: .
  • Insert : would make , resize to first, then insert .

Rehash (walk old bucket 0 head → tail: ; prepend each with ):

step key new slot that bucket after prepend
1 4
2 4
3 0

Then insert the pending key : , prepend into bucket 5.

Final :

  • Bucket 0:
  • Bucket 4:
  • Bucket 5:
  • Buckets 1,2,3,6,7: empty

Final . The chain of length 3 at slot 0 got flattened into single-item buckets. Why this closes the loop: you exercised hashing, prepend order, the strict- threshold, doubling, an explicit rehash traversal order, and re-reading — the entire machine in one problem.


Recap

Recall One-line recall for each level

L1 — ; chaining survives . ::: Definitions and the open-addressing-vs-chaining distinction. L2 — Insert order reverses (prepend); . ::: Apply the mechanics. L3 — Doubling gives total copy work, amortized. ::: Analyze cost growth. L4 — Chaining for deletes + ; hysteresis avoids thrashing. ::: Design under constraints. L5 — Successful search ; pigeonhole forces collisions. ::: Prove from scratch.

Connections

  • Chaining — linked lists in buckets, load factor, resizing — the topic these drills belong to.
  • Amortized Analysis — L3.2 and L4.2 lean on it.
  • Dynamic Arrays — same doubling / hysteresis trick.
  • Pigeonhole Principle — L5.2.
  • Hash Functions — L2.2's warning about bad hashes.
  • Open Addressing — the L4.1 contrast.
  • Linked Lists — the bucket itself.