8. Hashing
Introduction
Hashing is a fundamental technique in computer science that enables constant‑time average‑case performance for insert, delete, and search operations. By mapping keys to indices in an array via a hash function, we can store and retrieve data efficiently. This chapter covers the essential components—hash functions, hash tables, collision handling, load factor, and real‑world applications—providing both theoretical foundations and concrete examples.
Hash Functions
Definition and Desired Properties
A hash function h(k) converts a key k into an integer index suitable for addressing a hash table. To be effective, a hash function must satisfy three key properties:
- Deterministic: The same key always produces the same index.
- Uniform distribution: Keys are spread evenly across the table, minimizing collisions.
- Fast computation: The function should be O(1) in time.
Common Formulas
- Division method:
h(k) = k mod m, where m is the table size (preferably a prime number). - Multiplication method:
h(k) = floor(m * (k * A mod 1)), with a constant A satisfying 0 < A < 1 (oftenA = (√5‑1)/2 ≈ 0.618). - Universal hashing:
h(k) = ((a*k + b) mod p) mod m, where a and b are randomly chosen integers, p is a prime larger than the maximum possible key, and m is the table size.
Example: Division Method
Consider the key set {12, 44, 13, 88, 23} and a table size
m = 7.
h(12) = 12 mod 7 = 5h(44) = 44 mod 7 = 2h(13) = 13 mod 7 = 6h(88) = 88 mod 7 = 4h(23) = 23 mod 7 = 2→ collision at index 2.
Hash Table Structure
A hash table is an array of size m where each slot may hold a key‑value pair or be empty. Two primary implementation strategies exist:
Open Addressing
Elements are stored directly in the table. When a collision occurs, a probe sequence determines the next slot to examine.
Separate Chaining
Each table slot contains a reference to a linked list (or dynamic array) of all entries that hash to that index.
Basic Operations
- Insert: Compute
index = h(k). In chaining, append the pair to the list atindex; in open addressing, follow the probe sequence until an empty slot is found. - Search: Compute
index = h(k). Scan the chain (chaining) or follow the probe sequence (open addressing) until the key is found or an empty slot indicates absence. - Delete: Locate the element as in search. In chaining, remove the node from the list. In open addressing, mark the slot as a tombstone so future probes can skip it while still allowing insertions to reuse the space.
Collision Handling
Separate Chaining
Each bucket is a linked list; the average chain length equals the load factor α = n / m. Expected time complexities are:
- Insert: O(1) average.
- Search: O(1 + α) average.
- Delete: O(1 + α) average.
Example: with α = 0.75, the expected number of probes for a successful search is approximately 1 + 0.75 = 1.75.
Open Addressing
All elements reside in the table itself; performance degrades as the table fills. Common probe sequences include:
Linear Probing
h'(k,i) = (h(k) + i) mod m, where i is the probe number (0,1,2,…).
Quadratic Probing
h'(k,i) = (h(k) + c1*i + c2*i²) mod m, with constants c1 and c2 chosen to ensure full table coverage.
Double Hashing
h'(k,i) = (h1(k) + i*h2(k)) mod m, where h2(k) ≠ 0 for all keys. A typical choice is h2(k) = 1 + (k mod (m‑1)).
When the load factor approaches 1, the expected probe count grows dramatically. To maintain performance, a rehashing (resize) operation is triggered when α exceeds a threshold (commonly 0.7 for chaining, 0.5 for open addressing).
Load Factor
The load factor quantifies how full the hash table is:
α = n / m, where n is the number of stored elements and m is the table size.
It directly influences collision probability and thus the average operation cost.
- Good performance for chaining:
α ≤ 0.7. - Preferred for open addressing:
α ≤ 0.5to keep probe counts low. - Resizing strategy: When
αexceeds the threshold, allocate a new table (often size2*m) and re‑hash all existing keys into the new table.
Applications
Login Authentication
Systems store a username mapped to a hashed password (often‑salted hash value in a hash table. During login, the entered password is hashed with the same salt and compared to the stored hash, providing O(1) verification time.
Database Indexing
Hash indexes map key values directly to disk pages or memory locations, enabling point‑lookup queries in constant time. Unlike B‑tree indexes, hash indexes do not support efficient range scans but excel at exact‑match queries.
Caching (LRU Cache)
An LRU (Least Recently Used) cache can be built using a hash table that maps keys to nodes in a doubly linked list representing usage order. Both get and put operations run in O(1) average time: the hash table provides instant node access, while the list maintains recency.
Blockchain
Transaction identifiers are often cryptographic hashes (e.g., SHA‑256) stored in a hash table for rapid lookup of transaction status or Merkle proofs. Hash tables facilitate efficient verification of transaction inclusion in a block by allowing constant‑time retrieval of sibling hashes during Merkle‑tree traversal.
Summary
Hashing provides a powerful mechanism for achieving average‑case constant‑time operations when supported by a well‑designed hash function, appropriate collision resolution strategy, and careful load factor management. Understanding the trade‑offs between separate chaining and open addressing, knowing when to resize, and recognizing where hashing shines in real‑world systems are essential skills for any data structures and algorithms practitioner.