Look up a key in a static hash table whose hash function has no collisions for the stored key set.
Perfect hashing uses a hash function that maps a fixed set of keys to distinct table positions. Because no two stored keys collide, lookup checks one computed position and compares the stored key with the query key.
This method is useful for static dictionaries. The table is built once, then used for many fast lookups.
Problem
Given a static set of keys and a query key , determine whether exists in and return its associated value if present.
Model
A perfect hash function for is a function such that for any two distinct keys :
The table stores each key at position:
Algorithm
perfect_hash_lookup(T, k):
i = h(k)
if T[i] is occupied and T[i].key == k:
return T[i].value
return NOT_FOUNDExample
Suppose the stored keys are:
A perfect hash function maps them as follows:
| key | hash index |
|---|---|
| 12 | 0 |
| 25 | 3 |
| 31 | 1 |
| 44 | 2 |
Lookup for :
- compute
- check
- stored key is
- return its value
Lookup for :
- compute
- check that position
- if the stored key differs from , return not found
The final key comparison is still required because a perfect hash function only guarantees collision freedom for the stored set , not for every possible query key.
Correctness
For every stored key , the perfect hash function places it at exactly one unique position . Therefore, if the query key exists, lookup will compute the same index and find it there.
If the computed slot is empty or contains a different key, then the query key is not in the stored set.
Complexity
| case | time |
|---|---|
| lookup | |
| worst lookup |
The construction cost depends on the perfect hashing scheme. Lookup itself performs a constant number of hash computations and table accesses.
Space
For ordinary perfect hashing, may be larger than . Minimal perfect hashing improves this by using exactly slots.
When to Use
Perfect hashing is appropriate when:
- the key set is fixed
- lookups are frequent
- insertions and deletions are rare or absent
- worst case constant lookup is desired
It is less suitable for dynamic tables because changing the key set may require rebuilding the hash function.
Implementation
class PerfectHashTable:
def __init__(self, table, hash_func):
self.table = table
self.hash_func = hash_func
def get(self, key):
i = self.hash_func(key)
if i < 0 or i >= len(self.table):
return None
entry = self.table[i]
if entry is None:
return None
k, v = entry
if k == key:
return v
return Nonetype Entry[K comparable, V any] struct {
Key K
Value V
Used bool
}
type PerfectHashTable[K comparable, V any] struct {
Table []Entry[K, V]
Hash func(K) int
}
func (h *PerfectHashTable[K, V]) Get(key K) (V, bool) {
i := h.Hash(key)
if i < 0 || i >= len(h.Table) {
var zero V
return zero, false
}
e := h.Table[i]
if e.Used && e.Key == key {
return e.Value, true
}
var zero V
return zero, false
}