Foundations, Hash Functions & Load Factor
Universal hashing, avalanche effect, MurmurHash/xxHash principles, modulo prime bucket sizing, and load factor threshold theorem.
Reinforce your mental model: run, pause, and inspect live pointers with our interactive visualizer:
Topics Covered:
35. Hashing Principles & Direct Address Table Comparison • 36. Hash Functions & Uniform Distribution (SUHA, Division, Multiplication, Polynomial Rolling Hash) • 37. Collisions, The Birthday Paradox & Load Factor () • Production Implementations & Formal Derivations
Direct address tables offer constant-time retrieval by assigning every possible key a distinct physical array index, but demand catastrophic memory allocations when key universes are sparse. Hashing bridges this efficiency gap by compressing vast key spaces into compact array bounds through deterministic mathematical transformations. This chapter examines the Direct Address Dilemma, Simple Uniform Hashing Assumptions (SUHA), division and multiplication hash generators, polynomial rolling hashes for strings, the mathematical inevitability of collisions under the Birthday Paradox, and load factor () thresholds for dynamic rehashing.
Learning Objectives
#- Contrast direct addressing with compact hash tables and quantify the memory savings achieved across sparse key spaces.
- Formalize the Simple Uniform Hashing Assumption (SUHA) and evaluate the mathematical properties of division, multiplication, and polynomial rolling hash functions.
- Prove why hash collisions are mathematically unavoidable using Dirichlet's Pigeonhole Principle.
- Derive the Birthday Paradox collision threshold and explain why collisions occur far earlier than intuition suggests.
- Calculate the load factor and define dynamic table resizing (rehashing) triggers to preserve expected amortized bounds.
- Implement production-grade polynomial string hashing and universal hash generators across C++, Python, and Java.
Topic 35: Hashing Principles & Direct Addressing
#1. Conceptual Architecture: The Direct Address Dilemma
In an ideal computational model, data retrieval executes in time by using the search key directly as an array index. This pattern is known as Direct Addressing.
The Direct Addressing Dilemma:
Suppose an enterprise needs to store employee profiles indexed by a 9-digit Social Security Number (SSN: 000-00-0000 to 999-99-9999):
- Universe of Keys (): Contains possible keys ().
- Direct Address Table: Requires allocating a contiguous array of pointers. At 8 bytes per pointer, this demands of RAM!
- Sparsity Reality: If the company employs only workers, of the allocated memory sits permanently empty and wasted.
The Hashing Resolution:
Rather than allocating memory for every conceivable key in universe , allocate a compact table of size (e.g., slots, requiring mere kilobytes of RAM). A deterministic mathematical function , called a Hash Function, maps keys into table index slots:
Topic 36: Hash Functions & Uniform Distribution
#1. Desirable Properties of Production Hash Functions
#- Strict Determinism: For any identical key , must evaluate to the exact same integer every time across the process lifecycle.
- Simple Uniform Hashing Assumption (SUHA): Every key is equally likely to hash into any of the slots, independently of where any other key has hashed:
- Computational Efficiency: Evaluates in time for fixed-width numeric keys and time for strings of length .
- The Avalanche Effect: Flipping a single bit in the input key should alter roughly of the bits in the output hash code, preventing clustered hash values for sequential keys.
2. Classic Hash Function Algorithms
#A. The Division Method
- Rule for Table Size : Choose to be a prime number not close to powers of 2 or 10.
- Why Avoid Powers of Two ()? Computing simply isolates the lowest bits of (equivalent to a bitwise mask
k & (m - 1)). All higher-order bits are completely ignored!
B. The Multiplication Method (Knuth's Golden Ratio Method)
C. Polynomial Rolling Hash for Strings
A string is treated as a polynomial where character code units are coefficients evaluated at base :
Topic 37: Collisions, The Birthday Paradox & Load Factor ()
#1. The Inevitability of Collisions: The Pigeonhole Principle
#By Dirichlet's Pigeonhole Principle, if items are placed into containers and , at least one container must hold more than one item. Because , collisions are mathematically guaranteed to occur:
2. The Birthday Paradox & Collision Likelihood
#How many randomly chosen people must gather in a room before the probability that at least two share a birthday exceeds ? The mathematical answer is just 23 people!
Formal Mathematical Derivation:
Let be the number of inserted keys and be the number of hash table slots. The probability that all keys hash into distinct slots (zero collisions) is:
| Table Capacity () | 50% Collision Threshold () | Percentage of Table Utilized |
|---|---|---|
| (Days in Year) | ||
3. Production Implementations
#A. C++20 Polynomial String Hash with Avalanche Bit-Mixer
#include <string_view>
#include <cstdint>
class HashUtil {
public:
// Polynomial Rolling Hash for Strings
static uint64_t polynomial_string_hash(std::string_view s, uint64_t p = 53, uint64_t m = 1'000'000'007) {
uint64_t hash_val = 0;
uint64_t p_power = 1;
for (char c : s) {
uint64_t char_val = static_cast<unsigned char>(c) + 1;
hash_val = (hash_val + char_val * p_power) % m;
p_power = (p_power * p) % m;
}
return hash_val;
}
// SplitMix64 64-bit Integer Avalanche Hash (Used in fast hashtables)
static uint64_t splitmix64(uint64_t x) {
x += 0x9e3779b97f4a7c15ULL;
x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9ULL;
x = (x ^ (x >> 27)) * 0x94d049bb133111ebULL;
return x ^ (x >> 31);
}
};B. Python 3 Rolling Hash with Substring Slice O(1) Equality
class RollingHash:
"""Computes polynomial string hash and prefix hash powers for O(1) substring queries."""
def __init__(self, s: str, base: int = 53, mod: int = 1_000_000_007):
self.s = s
self.base = base
self.mod = mod
n = len(s)
self.prefix_hash = [0] * (n + 1)
self.power = [1] * (n + 1)
for i in range(n):
val = ord(s[i]) + 1
self.prefix_hash[i + 1] = (self.prefix_hash[i] * base + val) % mod
self.power[i + 1] = (self.power[i] * base) % mod
def query(self, left: int, right: int) -> int:
"""Returns the polynomial hash of substring s[left:right+1] in O(1) time."""
total = self.prefix_hash[right + 1]
subtract = (self.prefix_hash[left] * self.power[right - left + 1]) % self.mod
return (total - subtract + self.mod) % self.mod4. Key Takeaways
#- Direct Addressing vs. Hashing: Direct addressing trades infinite memory for lookups; hashing achieves expected performance in compact memory by mapping keys into .
- Prime Moduli: The classical division method utilizes prime table sizes to avoid harmonic bit-clustering.
- The Birthday Paradox: Collisions occur with probability after only insertions ( keys for ).
- Load Factor Governance: Maintaining guarantees expected operations across practical workloads.
Academic Attribution & References
#- Cormen, T. H., Leiserson, C. E., Rivest, R. L., & Stein, C. (2022). Introduction to Algorithms (4th ed.), Chapter 11: Hash Tables. MIT Press.
- Knuth, D. E. (1998). The Art of Computer Programming, Volume 3: Sorting and Searching (2nd ed.), Section 6.4: Hashing. Addison-Wesley.
- Mitzenmacher, M., & Upfal, E. (2017). Probability and Computing: Randomization and Probabilistic Techniques in Algorithms (2nd ed.), Chapter 5: Balls, Bins, and Random Graphs. Cambridge University Press.