DSA Notes
Master all collision resolution strategies for hash tables — separate chaining with linked lists, open addressing (linear probing, quadratic probing, double hashing), comparison of all methods, load factor effects, and when to use each approach.
Why Collisions Are Inevitable
By the pigeonhole principle: if you hash n keys into m slots where n > m, at least one slot must contain multiple keys. Even with n ≤ m, the birthday paradox tells us collisions become likely once n > √m.
For a table of size m = 365, collisions are likely when n > √365 ≈ 19 keys.
The table cannot avoid collisions — it can only handle them.
Strategy 2 — Open Addressing
All elements stored in the table itself — no external chains. When a collision occurs, probe a sequence of slots until an empty one is found.
Linear Probing
Linear probing clustering problem:
| Table | [_, _, A, B, C, _, D, _] (A,B,C,D hashed to same region) |
| idx 2: A ≠ E | try idx 3 |
| idx 3: B ≠ E | try idx 4 |
| idx 4: C ≠ E | try idx 5 |
| idx 5: empty | insert E |
| The cluster [A,B,C,D] grows with each insertion | "primary clustering" |
Quadratic Probing
def _probe_sequence(self, key):
"""Probe: h(k), h(k)+1, h(k)+4, h(k)+9, ... (i²)"""
start = self._hash(key)
for i in range(self.size):
yield (start + i * i) % self.sizeReduces primary clustering but introduces secondary clustering — all keys with the same initial hash follow the same probe sequence.
Double Hashing
Double hashing eliminates both primary and secondary clustering. Each key has its own probe sequence. Best performance among open addressing methods.
Tombstone (Lazy Deletion) Problem
With open addressing, you cannot simply mark a deleted slot as empty — this would break lookups for keys that probed past this slot.
| Table after insertions | [_, A, B, C, _, D, _] |
| Delete A: mark as DELETED (tombstone) | [_, DEL, B, C, _, D, _] |
| idx 1: DEL (not empty!) | continue probing |
| idx 2: B == B | found! ✓ |
| idx 1: None | STOP (would incorrectly return "not found"!) |
Tombstones work but accumulate over time. A full rehash (remove tombstones) is needed periodically.
Load Factor Effects on Performance
Chaining
α = 0.5: average chain = 0.5 → fast
α = 1.0: average chain = 1.0 → acceptable
α = 2.0: average chain = 2.0 → slower
No hard limit — chains can grow indefinitely
Open Addressing (Linear Probing)
α = 0.5: avg probes ≈ 1.5 → fast
α = 0.7: avg probes ≈ 2.0 → acceptable
α = 0.9: avg probes ≈ 5.5 → slow
α = 1.0: impossible (table full)
Formula: avg probes ≈ 1/(1-α) for lookup failure
This grows dramatically as α → 1
Typical thresholds
Python dict: rehash when α > 0.67
Java HashMap: rehash when α > 0.75
Open addressing: rehash when α > 0.5
Chaining vs Open Addressing Comparison
| Feature | Chaining | Open Addressing (Double Hash) |
|---|---|---|
| Load factor limit | No limit (just slower) | Must stay < 1 |
| Practical α | ≤ 1 recommended | ≤ 0.5-0.7 |
| Memory | Extra pointers + heap | Compact array |
| Cache performance | Poor (pointer chasing) | Excellent (array-based) |
| Deletion | Simple (remove from list) | Complex (tombstones) |
| Worst case | O(n) one big chain | O(n) full table scan |
| Typical use | Java HashMap | Python dict, open addressing tables |
Python's dict uses open addressing with a variant of linear probing (perturbed probing) for excellent cache performance.
Robin Hood Hashing
A clever improvement to open addressing: when inserting, a key can "steal" a slot from a key that is "richer" (closer to its home slot).
| If new key's displacement > current key's displacement | SWAP |
| (The new key is "poorer" | it needs the slot more) |
| Result | Maximum displacement across all keys is minimized. |
This makes worst-case lookup O(log n) with high probability — much better than standard open addressing.
Summary
| Method | Mechanism | Pros | Cons |
|---|---|---|---|
| Chaining | Linked list per slot | Simple, handles high α, easy delete | Extra memory, poor cache |
| Linear Probing | Step by 1 | Cache friendly | Primary clustering |
| Quadratic Probing | Step by i² | Less clustering | Secondary clustering, misses slots |
| Double Hashing | Two hash functions | No clustering | Slower hash computation |
| Robin Hood | Minimize displacement | Lower variance | More complex |
For most applications: Python's built-in dict (open addressing with smart probing) or Java's HashMap (chaining with TreeMap for long chains) is the right choice.
*Next Lesson: Chaining*
Exam Focus
Revise definitions, diagrams, examples, and short-answer points for Collision Handling — Chaining vs Open Addressing Complete Comparison.
Interview Use
Prepare one clear explanation, one practical example, and one common mistake for this Data Structures & Algorithms topic.
Search Terms
data-structures-algorithms, data structures & algorithms, data, structures, algorithms, hashing, collision, handling
Related Data Structures & Algorithms Topics