Hash tables & chaining
◈ 4 cardsExpected O(1) under uniform hashing, load factor α, chaining Θ(1+α), and why the worst case is still Θ(n).
The dictionary problem, solved on average
A hash table implements a dictionary — INSERT, SEARCH, DELETE — in expected time, beating the of a balanced BST when you only need membership and don't need ordered operations (MINIMUM, SUCCESSOR, range queries). It works by mapping a key from a huge universe into a small array of slots with a hash function , storing the element near index .
The contrast is with a direct-address table, which uses the key itself as the index: worst-case, but it needs space — four billion slots for 32-bit keys. Hashing compresses down to slots, paying for that compression with collisions.
Collisions are unavoidable
A collision is two distinct keys hashing to the same slot, . Whenever , collisions are guaranteed by the pigeonhole principle — more keys than slots forces a shared slot. So every hash table needs a collision-resolution strategy. The two in CLRS are chaining (this lesson) and open addressing (probe sequences within the array).
Chaining and the load factor
In chaining, each slot holds a linked list of all elements that hash to .
INSERT: prepend to the list at — .SEARCH: scan the list at — on average.DELETE: unhook from the list — given a pointer to the element.
The star of the analysis is the load factor : the average number of elements per slot. Under the independent uniform hashing assumption (each key equally and independently likely to land in any slot), the expected chain length is , so both successful and unsuccessful searches cost — the "" pays for computing the hash, the "" for walking the expected-length chain. Keep (resize when grows) and , so every operation is expected . This is exactly how Python dict works — it resizes when .
Two hash functions, and the adversary
The division method is , with chosen as a prime not close to a power of 2 (a power-of-2 would make depend only on 's low-order bits — bad when keys share bit patterns). The multiplication method is less sensitive to the choice of .
But any fixed hash function is defeatable: an adversary who knows picks keys all in one collision class, forcing every key into one slot and every operation to . The fix is universal hashing — choose randomly from a family at startup where for any distinct pair. Now no input is reliably bad, because the input is chosen before the hash function is known. (Same randomization idea as randomized quicksort: move the worst case off the input and onto a coin.)
Worked example — chaining with the division method, m = 4
Use and insert keys . The hashes are , , , . So 10, 14, 22 all collide into slot 2, forming a three-node chain, while 7 sits alone in slot 3 — load factor . Searching for 14 hashes to slot 2 and scans the chain until it matches; searching 99 () scans slot 3, doesn't find it, and returns nothing. The collision in slot 2 is exactly the chain-walk the analysis predicts — and a worst-case input where every key hashes to slot 2 would degrade SEARCH to , which is why universal (randomized) hashing matters.