Hashing and Hash Tables
此内容尚不支持你的语言。
Hash Function Fundamentals
Section titled “Hash Function Fundamentals”A hash function maps an input from a large domain to a smaller, fixed-size range. Formally, where is the universe of possible keys and is the table Size. The quality of a hash function determines the performance of every data structure built on top of it.
Desirable Properties
Section titled “Desirable Properties”| Property | Definition | Why It Matters |
|---|---|---|
| Deterministic | Same input always produces same output | Lookups must find the same bucket as inserts |
| Uniform | Each output is equally likely: for all | Minimises collisions |
| Avalanche | Flipping any input bit changes each output bit with probability | Small input changes produce unpredictable output changes |
| Efficient | Computable in where is the key length | Hash computation should not dominate lookup cost |
| Reversible | (For non-cryptographic use) Given a hash value, finding a preimage should not be easy | Prevents intentional collision attacks |
Avalanche Criterion
Section titled “Avalanche Criterion”The strict avalanche criterion requires that for any single-bit change in the input, each output bit Flips with probability exactly 0.5. This is a necessary condition for the hash function to be “random-looking.” A weaker but still useful property is that flipping any input bit changes the Output value significantly (not just one output bit).
Uniformity
Section titled “Uniformity”A perfectly uniform hash function distributes keys across buckets so that each bucket Contains approximately keys. In practice, we measure uniformity by hashing a large sample of Inputs and checking that the chi-squared statistic of the bucket distribution is close to what we would expect from a truly random distribution.
def uniformity_test(hash_func, keys, num_buckets): """ Test hash function uniformity with chi-squared statistic. Returns (chi_squared, p_value), high p_value means good uniformity. """ import math
buckets = [0] * num_buckets for key in keys: buckets[hash_func(key) % num_buckets] += 1
expected = len(keys) / num_buckets chi_sq = sum((b - expected) ** 2 / expected for b in buckets)
# Degrees of freedom = num_buckets - 1 # For a good hash, chi_sq should be close to degrees of freedom return chi_sqHash Functions for Integers
Section titled “Hash Functions for Integers”Multiplicative Hashing
Section titled “Multiplicative Hashing”Where is a constant in and is the table size. Knuth recommends . This avoids the problem of poor distribution when the Table size and key values share common factors.
def multiplicative_hash(k, m, A=0x9E3779B9): """ Multiplicative hash using a fixed-point approximation of golden ratio. A = 2^32 / golden_ratio, common choice in practice. Time: O(1) """ k = (k * A) & 0xFFFFFFFF return (k >> (32 - int(m).bit_length())) % mInteger Hashing (Murmur-style Mixing)
Section titled “Integer Hashing (Murmur-style Mixing)”For general-purpose hashing of integers, bit-mixing functions are preferred. These scramble the bits of the input so that small changes in the input produce large changes in the output.
def splitmix64(x): """ Fast integer hash function. Excellent avalanche properties. Used as the default in Java"s SplittableRandom. Time: O(1) """ x = (x + 0x9E3779B97F4A7C15) & 0xFFFFFFFFFFFFFFFF x = (x ^ (x >> 30)) * 0xBF58476D1CE4E5B9 & 0xFFFFFFFFFFFFFFFF x = (x ^ (x >> 27)) * 0x94D049BB133111EB & 0xFFFFFFFFFFFFFFFF x = x ^ (x >> 31) return xHash Functions for Strings
Section titled “Hash Functions for Strings”Polynomial Rolling Hash
Section titled “Polynomial Rolling Hash”Where is a prime (commonly 31, 37, or 257) and is (using unsigned integer Overflow). This is the basis for Java’s String.hashCode() and many other implementations.
def polynomial_hash(s, p=31, mod=(1 << 64)): """ Polynomial rolling hash for strings. Java uses p=31, mod=2^31-1. Time: O(len(s)) """ h = 0 for c in s: h = (h * p + ord(c)) % mod return hCross-References
Section titled “Cross-References”- Linked Lists, Stacks, and Queues: Alternative linear data structures that complement hash tables for different use cases.
- Sorting Algorithms: Covers comparison-based and non-comparison sorting, which can be combined with hash-based techniques.
- Dynamic Programming: Explores overlapping subproblems and optimal substructure, concepts related to hash-based memoisation.
Intuition
Section titled “Intuition”Hash tables solve the fundamental problem of mapping keys to values with O(1) average-case lookup, insert, and delete. The idea is simple: a hash function converts a key into an array index, and you store the value at that index. The challenge is collisions, two different keys hashing to the same index. Separate chaining (each bucket holds a list) and open addressing (probe for the next empty slot) are the two main strategies. The load factor (elements/buckets) controls performance: keep it below 0.75 and operations stay O(1) amortized. When the load factor gets too high, you resize the table (in standard practice doubling it) and rehash everything, this is O(n) but happens rarely enough that the amortized cost per insert is still O(1).
Probabilistic hash-based structures trade accuracy for space efficiency. A bloom filter uses a bit array and multiple hash functions to test set membership with configurable false positive rates, it can say “definitely not in the set” or “probably in the set” but never “definitely in the set.” Count-min sketch estimates frequencies by maintaining multiple counter arrays, always overestimating but never by more than a predictable amount. HyperLogLog estimates the number of distinct elements in a stream using just kilobytes of memory by tracking the position of the leftmost 1-bit in hash values. These structures are invaluable in distributed systems where exact answers require too much memory or network communication.
Consistent hashing solves the distributed systems problem of mapping keys to servers with minimal redistribution when servers are added or removed. Instead of modulo hashing (which remaps nearly all keys when the server count changes), consistent hashing places both keys and servers on a ring. Each key maps to the nearest server clockwise. Virtual nodes (multiple positions per physical server) ensure even distribution. This is how DynamoDB, Cassandra, and content delivery networks distribute data across thousands of servers while minimizing the disruption when scaling up or down.