Algorithm Search⌘K

Proofs: CHD perfect hashing

These are the human proofs of CHD perfect hashing, at full length.

CHD evaluates in O(1) time, uses O(n) words, and builds in O(n) expected time in an idealized model sketch

This proof shows: time.expected, space.extra.

The model is the word RAM, with keys and hash values of O(1)O(1) words. Let λ=n/r\lambda = n/r be the average bucket size and α=n/m\alpha = n/m the load.

Claim 1 (evaluation). CHD-EVAL takes O(1)O(1) time in the worst case.

Proof. Lines L12 and L13 compute gg, f1f_1 and f2f_2 once each, read σ[i]\sigma[i], and do one division, one multiplication, two additions and one reduction. With σ\sigma stored as a plain array, the read is one memory access. □\square

Claim 2 (plain space). The function takes r+O(1)r + O(1) words: the array σ\sigma of r=⌈n/λ⌉r = \lceil n/\lambda \rceil entries, each less than m2m^2, and the descriptions of gg, f1f_1 and f2f_2. For a constant λ\lambda this is O(n)O(n) words. The build uses O(n+m+r)O(n + m + r) more words for the buckets and the marks of the slots. □\square

Lemma 3 (cost of one attempt). One attempt of lines L2 to L9 takes O(n+m+r+∑i∣Bi∣ ti)O(n + m + r + \sum_i |B_i| \, t_i) time, where tit_i is the number of indices that line L7 tests for bucket ii.

Proof. Line L3 takes O(n+r)O(n + r) time. Line L4 takes O(m)O(m). The order of line L5 is a sort of rr sizes between 0 and nn, which counting sort does in O(n+r)O(n + r). One test of line L7 computes ∣Bi∣|B_i| slots, and checks that they are free and distinct in O(∣Bi∣)O(|B_i|) time: it puts a temporary mark on each slot, stops at a slot that is used or already marked, and then removes the temporary marks. Line L9 takes O(∣Bi∣+1)O(|B_i| + 1). Summing over the buckets gives the claim. □\square

Lemma 4 (expected trials, idealized model). Assume that gg is fully random, and that for each bucket the slot vectors (sℓ(x))x∈Bi(s_\ell(x))_{x \in B_i} for ℓ=0,1,2,…\ell = 0, 1, 2, \ldots are independent, each with independent uniform entries in [m][m], and independent of everything that happens before the bucket is placed. Let α≤α0<1\alpha \le \alpha_0 < 1 for a constant α0\alpha_0, and ignore the cap ℓ<m2\ell < m^2. Then E[∑i∣Bi∣ ti]≤nc eλ(c−1)E[\sum_i |B_i| \, t_i] \le n c \, e^{\lambda (c - 1)}, where c=1/(1−α0)c = 1/(1 - \alpha_0).

Proof. Consider bucket ii with k=∣Bi∣≥1k = |B_i| \ge 1 keys, and let uu be the number of used slots when it is placed. The buckets not yet placed hold at least the kk keys of BiB_i, so u≤n−ku \le n - k. A test succeeds when kk independent uniform slots are distinct and free, which has probability

(m−u)(m−u−1)⋯(m−u−k+1)mk≥(m−n+1m)k≥(1−α0)k,\frac{(m - u)(m - u - 1) \cdots (m - u - k + 1)}{m^k} \ge \Big( \frac{m - n + 1}{m} \Big)^k \ge (1 - \alpha_0)^k,

since each factor is at least m−u−k+1≥m−n+1m - u - k + 1 \ge m - n + 1. The tests are independent, so tit_i is geometric and E[ti∣g]≤ckE[t_i \mid g] \le c^k. So E[∑i∣Bi∣ti∣g]≤∑i∣Bi∣c∣Bi∣E[\sum_i |B_i| t_i \mid g] \le \sum_i |B_i| c^{|B_i|}. For fully random gg, each ∣Bi∣|B_i| is binomial with nn trials and success probability 1/r1/r, and for such a KK, E[KcK]=(n/r) c (1+(c−1)/r)n−1≤λc e(c−1)n/rE[K c^K] = (n/r) \, c \, (1 + (c - 1)/r)^{n - 1} \le \lambda c \, e^{(c - 1) n / r}. Summing over the rr buckets gives rλc eλ(c−1)=nc eλ(c−1)r \lambda c \, e^{\lambda (c - 1)} = n c \, e^{\lambda (c - 1)}. □\square

For constant λ\lambda and α0\alpha_0, Lemmas 3 and 4 give O(n+m)=O(n)O(n + m) = O(n) expected time for one attempt in the idealized model. An attempt fails only if some bucket needs more than m2m^2 tests, which by Markov's inequality has probability at most nc eλ(c−1)/m2n c \, e^{\lambda(c - 1)} / m^2, less than 1/21/2 once mm is large enough. Then the expected number of attempts is at most 2, and the expected build time is O(n)O(n).

What is a sketch here. Three steps are not proved on this page.

  1. The idealized model. The real slot vectors sℓs_\ell for different ℓ\ell are built from the same values f1(x)f_1(x) and f2(x)f_2(x), so they are not independent: the indices with the same d0d_0 are cyclic shifts of each other. The analysis of Belazzougui, Botelho and Dietzfelbinger (ESA 2009, LNCS 5757, pages 682 to 693) treats the hash functions as fully random and analyzes the scheme with its own assumptions; we do not reproduce it. The bound of Lemma 4 also grows like eλα0/(1−α0)e^{\lambda \alpha_0/(1 - \alpha_0)} and does not use the order of line L5; the paper's analysis of the placement in order of decreasing size gives much better constants.
  2. The compressed space. The statement that the compressed σ\sigma takes about 2 bits for each key, close to the lower bound for perfect hash functions, is from the same paper (its abstract gives about 1.4 bits for each key for m=1.23nm = 1.23n and about 1.98 bits for m=1.01nm = 1.01n), together with the O(1)O(1) access time of its compressed code.
  3. The minimal case m=nm = n. Lemma 4 needs α0<1\alpha_0 < 1. With m=nm = n, the last buckets are placed in a table with very few free slots; a bucket of one key still always fits within mm tests, by the index ℓ=(t−f1(x)) mod m\ell = (t - f_1(x)) \bmod m for a free slot tt, but no bound for buckets of two or more keys is proved here.

CHD-BUILD returns a perfect hash function, minimal when m = n, and ends with probability 1 for fully random hash functions full

This proof shows: correctness, termination.

Let SS be a set of nn distinct keys, let m≥max⁡(n,1)m \ge \max(n, 1), and let r≥1r \ge 1. For an index ℓ∈{0,…,m2−1}\ell \in \{0, \ldots, m^2 - 1\} write sℓ(x)=(f1(x)+⌊ℓ/m⌋f2(x)+(ℓ mod m)) mod ms_\ell(x) = (f_1(x) + \lfloor \ell/m \rfloor f_2(x) + (\ell \bmod m)) \bmod m. Then CHD-EVAL(x)=sσ[g(x)](x)(x) = s_{\sigma[g(x)]}(x), by lines L12 and L13.

Lemma 1 (invariant of the placement loop). Fix one attempt, that is, one pass of lines L2 to L9 with fixed gg, f1f_1 and f2f_2. After each bucket that line L9 places, the following holds. Let PP be the set of buckets placed so far. The slots sσ[i](x)s_{\sigma[i]}(x), for i∈Pi \in P and x∈Bix \in B_i, are pairwise distinct, and the used slots are exactly these slots.

Proof. By induction on the buckets in the order of line L5. Before the first one, PP is empty and line L4 marks all slots free. Suppose the claim holds for PP, and line L9 places bucket ii with index ℓ\ell. The loop of line L7 stopped at ℓ<m2\ell < m^2 (otherwise line L8 would have left the loop), so its condition was false: the slots sℓ(x)s_\ell(x), x∈Bix \in B_i, are pairwise distinct and none of them is used. By the hypothesis, the used slots are those of the keys of PP, so the new slots are also distinct from all of these. Line L9 sets σ[i]=ℓ\sigma[i] = \ell and marks exactly the new slots, so the claim holds for P∪{i}P \cup \{i\}. An empty bucket is placed with ℓ=0\ell = 0 and adds no slot. □\square

Theorem 1 (the output is perfect). If CHD-BUILD returns, then CHD-EVAL maps UU into [m][m], is one to one on SS, and, when m=nm = n, maps SS onto [n][n].

Proof. Each value is reduced mod mm, so it is in [m][m]. CHD-BUILD returns only after an attempt in which line L8 never set ok to false, so every bucket was placed. The buckets B0,…,Br−1B_0, \ldots, B_{r-1} partition SS, since each key lies in exactly the bucket g(x)g(x). By Lemma 1 with P=[r]P = [r], the slots sσ[g(x)](x)s_{\sigma[g(x)]}(x) for x∈Sx \in S are pairwise distinct, and these are the values of CHD-EVAL. So CHD-EVAL is one to one on SS. If m=nm = n, its nn distinct values on SS lie in a set of nn elements, so they are all of [n][n]. □\square

Lemma 2 (a sufficient condition). If f1f_1 is one to one on SS, the attempt succeeds.

Proof. We show that every bucket is placed with ℓ=0\ell = 0, the first index that line L7 tests. For ℓ=0\ell = 0, s0(x)=f1(x)s_0(x) = f_1(x). When bucket ii comes, the values f1(x)f_1(x) for x∈Bix \in B_i are pairwise distinct. By Lemma 1 the used slots are the values f1(y)f_1(y) of the keys yy of the buckets already placed, all different from the keys of BiB_i, so none of them equals any f1(x)f_1(x), x∈Bix \in B_i. So the condition of line L7 is false at ℓ=0\ell = 0. □\square

Theorem 2 (termination). If each attempt draws f1f_1 uniformly from all functions U→[m]U \to [m], independently of the earlier attempts, then CHD-BUILD ends with probability 1, after at most mn(m−n)!/m!m^n (m - n)!/m! attempts in expectation.

Proof. Each attempt runs at most rr buckets with at most m2m^2 values of ℓ\ell each, so it ends. By Lemma 2, it succeeds when f1f_1 is one to one on SS, which happens with probability q=m(m−1)⋯(m−n+1)/mn>0q = m(m - 1) \cdots (m - n + 1)/m^n > 0 since m≥nm \ge n. The attempts are independent, so the number of attempts is at most a geometric random variable with mean 1/q1/q, which is finite. □\square

The bound of Theorem 2 is very weak; it only shows that the restart loop cannot run forever. The expected number of attempts in practice is close to 1, see the complexity proof. For the polynomials of degree 2 of the Python version, Theorem 1 still holds, since it does not depend on how the functions are chosen, but Theorem 2 is not proved.

Back to CHD perfect hashing