Skip to content

Perfect Hashing Lookup

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 SS and a query key kk, determine whether kk exists in SS and return its associated value if present.

Model

A perfect hash function for SS is a function hh such that for any two distinct keys a,bSa, b \in S:

h(a)h(b) h(a) \neq h(b)

The table stores each key kSk \in S at position:

T[h(k)] T[h(k)]

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_FOUND

Example

Suppose the stored keys are:

S=12,25,31,44 S = {12, 25, 31, 44}

A perfect hash function maps them as follows:

keyhash index
120
253
311
442

Lookup for 3131:

  • compute h(31)=1h(31) = 1
  • check T[1]T[1]
  • stored key is 3131
  • return its value

Lookup for 1818:

  • compute h(18)h(18)
  • check that position
  • if the stored key differs from 1818, return not found

The final key comparison is still required because a perfect hash function only guarantees collision freedom for the stored set SS, not for every possible query key.

Correctness

For every stored key kSk \in S, the perfect hash function places it at exactly one unique position T[h(k)]T[h(k)]. 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

casetime
lookupO(1)O(1)
worst lookupO(1)O(1)

The construction cost depends on the perfect hashing scheme. Lookup itself performs a constant number of hash computations and table accesses.

Space

O(m) O(m)

For ordinary perfect hashing, mm may be larger than nn. Minimal perfect hashing improves this by using exactly nn 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 None
type 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
}