Algorithm Search⌘K

Proofs: Dynamic perfect hashing

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

Dynamic perfect hashing has O(1) worst-case search, O(1) expected amortized updates and O(n) space full

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

The model is the word RAM with p<2wp < 2^w, so a hash value costs O(1)O(1) time. The structure starts with REHASH-ALL(∅)(\emptyset), and the sequence of operations is fixed before the random choices (an oblivious adversary). We use the invariants (D1) to (D5), Fact (U), Lemma 1 and the corollary M≤2∣S∣+1M \le 2|S| + 1 of the correctness proof. The sum ∑i2Li2\sum_i 2L_i^2 of line L8 is kept in a counter, set by line L23 and changed at line L7, so line L8 takes O(1)O(1) time. The constants 64 and 512 are those of this page, not of the paper of Dietzfelbinger and others; the structure of the proof (rebuild a bucket after a collision, double a full bucket, start a new phase after a fixed number of updates or when the space is too large) follows that paper.

A phase is the time from one call of REHASH-ALL to the next. In a phase, hh and MM are fixed. As in the correctness proof, an update is effective when it changes SS. Whether an update is effective depends only on SS, so the sequence of sets SS is fixed by the sequence of operations and does not depend on the random choices.

Claim 1 (search). DPH-SEARCH takes O(1)O(1) time in the worst case. So does each operation that is not effective.

Proof. Lines L14 and L15 compute two hash values and compare one key. Lines L1 and L10 call it once. □\square

Claim 2 (space). In each state the structure uses O(∣S∣+1)O(|S| + 1) words.

Proof. The first level stores MM entries of O(1)O(1) words each (SjS_j is not stored apart from TjT_j; the Python version keeps it as a set of size ∣Sj∣|S_j|, which adds O(∣S∣)O(|S|)). The second level has ∑j2Lj2≤512M\sum_j 2L_j^2 \le 512M slots by (D4). By the corollary, M≤2∣S∣+1M \le 2|S| + 1. □\square

Lemma 3 (one rebuild of a bucket). Given any history, REBUILD-BUCKET(j)(j) takes O(∣Sj∣+1)O(|S_j| + 1) expected time, plus O(Lj2)O(L_j^2) if line L16 allocates a new array.

Proof. By Lemma 1 of the correctness proof, each trial succeeds with probability more than 3/43/4, whatever happened before, because its draws are fresh. So the expected number of trials is less than 4/34/3. A trial computes ∣Sj∣|S_j| hash values and tests that they are distinct. The test takes O(∣Sj∣)O(|S_j|) time with the table itself: write a mark in each slot, stop at a slot that is already marked, then clear the marked slots. Line L16 clears ∣Sj∣|S_j| slots, or allocates 2Lj22L_j^2 slots. Line L19 takes O(∣Sj∣+1)O(|S_j| + 1). □\square

Lemma 4 (a collision is unlikely). Consider an effective insert of xx that reaches line L5 with cj≤Ljc_j \le L_j for j=h(x)j = h(x). The probability that the slot Tj[hj(x)]T_j[h_j(x)] is in use, given the history up to this point, is at most 2/(3Lj)2/(3L_j).

Proof. Look at the last rebuild of bucket jj before this insert, and let HH be the history up to its start. Given HH, the function hh of the phase and the sequence of effective updates are fixed. Let A0A_0 be SjS_j at that rebuild, and let BB be A0A_0 together with the keys that the effective updates insert into bucket jj from then up to and including xx. So BB is fixed given HH, and ∣B∣≤cj≤Lj|B| \le c_j \le L_j, since cjc_j was set to ∣A0∣|A_0| by the rebuild and raised by one at each of these inserts.

The rebuild draws functions gg from the family with s=2Lj2s = 2L_j^2 slots until gg is one-to-one on A0A_0, so the accepted hjh_j is a raw draw gg conditioned on the event GG that gg is one-to-one on A0A_0. Let EE be the event that bucket jj is not rebuilt between the rebuild and this insert. A rebuild in between could come from line L9 or from REHASH-ALL. The rebuilds of cases 3 and 4 of the correctness proof, and REHASH-ALL at lines L3 and L12, depend only on counters, which are fixed given HH; REHASH-ALL at line L8 depends only on the limits of the other buckets, because LjL_j changes only at a rebuild of bucket jj, and these limits depend on draws for the other buckets, which are independent of gg. So, given HH, EE is the intersection of an event EjE_j that depends only on gg (that g∈Gg \in G and that no insert into bucket jj before xx found its slot in use) and an event independent of gg, which cancels out of the conditional probability.

Let II be the event that gg is one-to-one on BB. Then I⊆EjI \subseteq E_j: if gg is one-to-one on BB, it is one-to-one on A0A_0, and each key placed before xx found a free slot, since the slots in use hold other keys of BB. By Lemma 1 of the correctness proof applied to BB, Pr⁡[I]>3/4\Pr[I] > 3/4. Let DD be the event that g(x)=g(y)g(x) = g(y) for some y∈B∖{x}y \in B \setminus \{x\}. The slot of xx is in use only if it holds a key of B∖{x}B \setminus \{x\}, so the event of the claim implies DD. By (U), Pr⁡[D]≤(∣B∣−1)/s<Lj/(2Lj2)\Pr[D] \le (|B| - 1)/s < L_j / (2L_j^2). So

Pr⁡[slot in use∣H,E]≤Pr⁡[D∩Ej]Pr⁡[Ej]≤Pr⁡[D]Pr⁡[I]<1/(2Lj)3/4=23Lj.□\Pr[\text{slot in use} \mid H, E] \le \frac{\Pr[D \cap E_j]}{\Pr[E_j]} \le \frac{\Pr[D]}{\Pr[I]} < \frac{1/(2L_j)}{3/4} = \frac{2}{3L_j}. \qquad \square

Lemma 5 (rebuilds without doubling). The rebuilds of line L9 in cases 2 and 3 of the correctness proof take O(1)O(1) expected amortized time for each effective update.

Proof. Case 2 (a collision with cj≤Ljc_j \le L_j). By Lemma 3 the rebuild costs O(∣Sj∣+1)=O(Lj)O(|S_j| + 1) = O(L_j) expected time; no array is allocated, since LjL_j did not change. By Lemma 4 it happens with probability at most 2/(3Lj)2/(3L_j). So its expected cost, for each insert, is O(1)O(1).

Case 3 (cj>Ljc_j > L_j and ∣Sj∣≤Lj/2|S_j| \le L_j/2). The cost is O(∣Sj∣+1)=O(Lj)O(|S_j| + 1) = O(L_j) expected time. Since the last rebuild of bucket jj, let c0=∣Sj∣c_0 = |S_j| at that rebuild, and let i≥1i \ge 1 and dd be the numbers of effective inserts and deletes in bucket jj. Then cj=c0+i≥Lj+1c_j = c_0 + i \ge L_j + 1 and ∣Sj∣=c0+i−d≤Lj/2|S_j| = c_0 + i - d \le L_j/2. If i<Lj/2i < L_j/2, then c0>Lj/2c_0 > L_j/2 and d=c0+i−∣Sj∣>Lj/2d = c_0 + i - |S_j| > L_j/2. In both cases i+d≥Lj/2i + d \ge L_j/2, so the cost is O(i+d)O(i + d). Charge it to these i+di + d updates. Each update is charged at most once, to the first rebuild of its bucket after it, since a rebuild resets the count. □\square

Lemma 6 (doubling). Fix a phase, and let kjk_j be the number of distinct keys that are in SjS_j at some time during the phase. Then at each time of the phase, Lj≤4max⁡(kj,1)L_j \le 4\max(k_j, 1). The arrays allocated in the phase, including those of the REHASH-ALL that starts it, have at most 43⋅512M\frac{4}{3} \cdot 512M slots in total, and the rebuilds of case 4 take O(M)O(M) expected time in total.

Proof. REHASH-ALL sets Lj=2max⁡(∣Sj∣,1)≤2max⁡(kj,1)L_j = 2\max(|S_j|, 1) \le 2\max(k_j, 1). Line L7 doubles LjL_j to 2Lj2L_j only when ∣Sj∣>Lj/2|S_j| > L_j/2, so kj≥∣Sj∣>Lj/2k_j \ge |S_j| > L_j/2 and the new limit 2Lj<4kj2L_j < 4k_j. A bucket allocates an array at the start of the phase and after each doubling that passes line L8, of sizes 2L2,2(2L)2,2(4L)2,…2L^2, 2(2L)^2, 2(4L)^2, \ldots, which grow by a factor of 4. Their sum is less than 43\frac{4}{3} times the last one. The last ones are the current sizes, whose sum is at most 512M512M by (D4); a doubling that fails line L8 allocates nothing in this phase. So the total is at most 43⋅512M\frac{4}{3} \cdot 512M slots, and allocating them takes O(M)O(M) time. A rebuild of case 4 costs, by Lemma 3, O(∣Sj∣+1)=O(Lj)O(|S_j| + 1) = O(L_j) expected time plus its allocation of 2Lj2≥Lj2L_j^2 \ge L_j slots, so these rebuilds take O(M)O(M) expected time in total. □\square

Lemma 7 (REHASH-ALL). REHASH-ALL(S′)(S') takes O(M)O(M) expected time, where M=max⁡(∣S′∣,1)M = \max(|S'|, 1), given any history.

Proof. By the proof of Lemma 2 of the correctness proof, a trial of lines L21 and L22 succeeds with probability more than 5/85/8, so the expected number of trials is less than 8/58/5, and each takes O(∣S′∣+M)=O(M)O(|S'| + M) = O(M) time. Line L23 then calls REBUILD-BUCKET for MM buckets. By Lemma 3, this takes O(∑j(∣Sj∣+1))=O(M)O(\sum_j (|S_j| + 1)) = O(M) expected time plus the allocation of ∑j2Lj2≤64M\sum_j 2L_j^2 \le 64M slots. □\square

Lemma 8 (line L8 rarely ends a phase). Let FF be the history up to the start of a phase, before its first-level function is drawn. Then the probability, given FF, that the phase ends at line L8 is less than 0.640.64.

Proof. Given FF, the set S′S' at the start, with n0=∣S′∣≤Mn_0 = |S'| \le M keys, and the sequence of effective updates after it, are fixed. Let KK be S′S' together with the keys inserted by the next ⌈M/2⌉\lceil M/2 \rceil effective updates. Then ∣K∣≤n0+⌈M/2⌉≤2M|K| \le n_0 + \lceil M/2 \rceil \le 2M. The phase reaches line L4 for at most ⌈M/2⌉\lceil M/2 \rceil effective updates, because the next one makes u>⌈M/2⌉u > \lceil M/2 \rceil and ends the phase at line L3 or L12. So each key that is ever in a bucket during the phase is in KK. Let kj∗=∣{x∈K:h(x)=j}∣k^*_j = |\{x \in K : h(x) = j\}|, so kj≤kj∗k_j \le k^*_j. By Lemma 6, at each time of the phase,

∑j2Lj2≤32∑jmax⁡(kj∗,1)2≤32(∑j(kj∗)2+M).\sum_j 2L_j^2 \le 32 \sum_j \max(k^*_j, 1)^2 \le 32 \Big( \sum_j (k^*_j)^2 + M \Big).

So line L8 can end the phase only if ∑j(kj∗)2>15M\sum_j (k^*_j)^2 > 15M. For a raw draw gg of the first-level function, the identity ∑j(kj∗)2=∣K∣+2C\sum_j (k^*_j)^2 = |K| + 2C, with CC the number of colliding pairs of KK, and (U) give E[∑j(kj∗)2]≤∣K∣+∣K∣(∣K∣−1)/M≤2M+2(2M−1)<6ME[\sum_j (k^*_j)^2] \le |K| + |K|(|K| - 1)/M \le 2M + 2(2M - 1) < 6M, so by Markov's inequality Pr⁡[∑j(kj∗)2>15M]<6/15=0.4\Pr[\sum_j (k^*_j)^2 > 15M] < 6/15 = 0.4. The function hh of the phase is a raw draw conditioned on the acceptance test of line L22, which passes with probability more than 5/85/8 (Lemma 7). So the probability is less than 0.4/(5/8)=0.640.4/(5/8) = 0.64. □\square

Theorem (time). A sequence of NN operations, from the empty structure, takes O(N+1)O(N + 1) expected time in total. So each operation takes O(1)O(1) expected amortized time.

Proof. Each operation does O(1)O(1) work outside the rebuilds (Claim 1, the counter of line L8). The rebuilds of cases 2 and 3 cost O(N)O(N) expected in total (Lemma 5). The rest of the cost is in the phases: each phase ii, with MiM_i buckets, costs O(Mi)O(M_i) expected time given its history FiF_i, for its REHASH-ALL (Lemma 7) and its doublings (Lemma 6). So the expected total is O(N+1+E[∑iMi])O(N + 1 + E[\sum_i M_i]). There are at most N+1N + 1 phases, since each phase after the first starts during an effective update.

Let QiQ_i be the indicator that phase ii exists and ends at line L8. MiM_i is fixed by FiF_i, so by Lemma 8, E[MiQi]≤0.64 E[Mi]E[M_i Q_i] \le 0.64\, E[M_i], which gives E[Mi]≤E[Mi(1−Qi)]/0.36E[M_i] \le E[M_i (1 - Q_i)] / 0.36. A phase with Qi=0Q_i = 0 either ends at line L3 or L12, and then it contains ⌈Mi/2⌉+1≥Mi/2\lceil M_i/2 \rceil + 1 \ge M_i / 2 effective updates of its own, or it is the last phase, and then Mi≤2∣S∣+1≤2N+1M_i \le 2|S| + 1 \le 2N + 1. The phases are disjoint, so

E[∑iMi]≤10.36E[∑iMi(1−Qi)]≤10.36(2N+2N+1)=O(N+1).□E\Big[\sum_i M_i\Big] \le \frac{1}{0.36} E\Big[\sum_i M_i (1 - Q_i)\Big] \le \frac{1}{0.36} \big( 2N + 2N + 1 \big) = O(N + 1). \qquad \square

The paper of Dietzfelbinger, Karlin, Mehlhorn, Meyer auf der Heide, Rohnert and Tarjan (SIAM Journal on Computing 23(4), 1994, pages 738 to 761) proves the same three bounds for its own version of the scheme; the constants and the accounting above are not taken from it.

Dynamic perfect hashing keeps its invariants and answers searches correctly full

This proof shows: correctness, termination.

Keys are in U={0,…,p−1}U = \{0, \ldots, p - 1\} with pp prime. The structure starts with REHASH-ALL(∅)(\emptyset). We say that an operation is effective when it changes SS: an insert of a key not in SS, or a delete of a key in SS. Only effective operations reach line L2 or L11.

Invariants. After each operation:

  • (D1) SS is the disjoint union of S0,…,SM−1S_0, \ldots, S_{M-1}, and Sj={x∈S:h(x)=j}S_j = \{x \in S : h(x) = j\}.
  • (D2) TjT_j has 2Lj22L_j^2 slots, hjh_j maps into them and is one-to-one on SjS_j, and the slots of TjT_j that are not NIL hold exactly the keys of SjS_j, each xx in slot hj(x)h_j(x).
  • (D3) ∣Sj∣≤cj≤Lj|S_j| \le c_j \le L_j.
  • (D4) ∑j2Lj2≤512M\sum_j 2L_j^2 \le 512M.
  • (D5) u≤⌈M/2⌉u \le \lceil M/2 \rceil.

Fact (U). For each m≥1m \ge 1 and distinct x,y∈Ux, y \in U, Pr⁡[ha,b,m(x)=ha,b,m(y)]≤1/m\Pr[h_{a,b,m}(x) = h_{a,b,m}(y)] \le 1/m. For m<pm < p this is the universality of the Carter and Wegman family. For m≥pm \ge p, the reduction mod mm does nothing, and (ax+b) mod p≠(ay+b) mod p(ax + b) \bmod p \ne (ay + b) \bmod p because a≠0a \ne 0 and pp is prime, so the probability is 0.

Lemma 1 (REBUILD-BUCKET). If ∣Sj∣≤Lj|S_j| \le L_j when REBUILD-BUCKET(j)(j) starts, it ends with probability 1, and afterwards (D2) holds for bucket jj and cj=∣Sj∣≤Ljc_j = |S_j| \le L_j.

Proof. A random hjh_j into s=2Lj2s = 2L_j^2 slots has, by (U) and linearity of expectation, an expected number of colliding pairs in SjS_j of at most (∣Sj∣2)/s≤(Lj2)/(2Lj2)<1/4\binom{|S_j|}{2}/s \le \binom{L_j}{2}/(2L_j^2) < 1/4. By Markov's inequality, a trial fails with probability less than 1/41/4, and the trials are independent, so the loop of lines L17 and L18 ends with probability 1. Line L16 leaves TjT_j with 2Lj22L_j^2 slots, all NIL: a new array is NIL everywhere, and an old one has, by (D2) before the rebuild, no key outside the slots hj(y)h_j(y) of the old keys yy, which line L16 clears (the new key of line L4, if any, was never stored). Line L19 then stores each key of SjS_j in its own slot, since hjh_j is one-to-one on SjS_j, and sets cj=∣Sj∣c_j = |S_j|. □\square

Lemma 2 (REHASH-ALL). REHASH-ALL(S′)(S') ends with probability 1 and establishes (D1) to (D5) for S=S′S = S'.

Proof. Let n0=∣S′∣n_0 = |S'| and M=max⁡(n0,1)M = \max(n_0, 1). If n0=0n_0 = 0, then M=1M = 1 and the sum of line L22 is 8≤648 \le 64. If n0≥1n_0 \ge 1, then M=n0M = n_0, and with nj=∣Sj∣n_j = |S_j|, ∑jnj2=n0+2C\sum_j n_j^2 = n_0 + 2C, where CC is the number of colliding pairs, so by (U), E[∑jnj2]≤n0+2(n02)/M=2n0−1E[\sum_j n_j^2] \le n_0 + 2\binom{n_0}{2}/M = 2n_0 - 1. Since max⁡(t,1)2≤t2+1\max(t, 1)^2 \le t^2 + 1, E[∑j8max⁡(nj,1)2]≤8(2n0−1+M)<24ME[\sum_j 8\max(n_j, 1)^2] \le 8(2n_0 - 1 + M) < 24M, and by Markov's inequality a trial of lines L21 and L22 fails with probability less than 24/64=3/824/64 = 3/8. So the loop ends with probability 1. Line L21 makes (D1) hold. Line L23 sets Lj=2max⁡(nj,1)≥njL_j = 2\max(n_j, 1) \ge n_j, so Lemma 1 applies to each bucket and gives (D2) and (D3). Then ∑j2Lj2=∑j8max⁡(nj,1)2≤64M≤512M\sum_j 2L_j^2 = \sum_j 8 \max(n_j, 1)^2 \le 64M \le 512M, which is (D4), and u=0u = 0 gives (D5). □\square

Lemma 3 (search). If (D1) and (D2) hold, DPH-SEARCH(x)(x) returns true exactly when x∈Sx \in S.

Proof. Let j=h(x)j = h(x). If x∈Sx \in S, then x∈Sjx \in S_j by (D1), and Tj[hj(x)]=xT_j[h_j(x)] = x by (D2), so line L15 returns true. If x∉Sx \notin S, then x∉Sjx \notin S_j, and by (D2) each non-NIL slot of TjT_j holds a key of SjS_j, so Tj[hj(x)]≠xT_j[h_j(x)] \ne x and line L15 returns false. □\square

Theorem. Each operation ends with probability 1 and keeps (D1) to (D5). So each search, and the test of lines L1 and L10, is correct.

Proof. By induction on the operations. The start is REHASH-ALL, covered by Lemma 2. Assume (D1) to (D5) before an operation. A search changes nothing. By Lemma 3, lines L1 and L10 return exactly for the operations that are not effective, which change nothing.

Effective insert of x∉Sx \notin S. If line L3 calls REHASH-ALL, Lemma 2 gives the invariants for S∪{x}S \cup \{x\}. Otherwise u≤⌈M/2⌉u \le \lceil M/2 \rceil after line L2, which keeps (D5). Line L4 adds xx to Sh(x)S_{h(x)} and nothing else, which keeps (D1), and raises ∣Sj∣|S_j| and cjc_j by 1 each, which keeps ∣Sj∣≤cj|S_j| \le c_j. Now there are four cases.

  1. Line L5 stores xx. Then cj≤Ljc_j \le L_j, which is (D3). The slot hj(x)h_j(x) was NIL, so by (D2) no key of Sj∖{x}S_j \setminus \{x\} has this slot, hjh_j is one-to-one on SjS_j, and the stored keys are exactly SjS_j. (D4) is unchanged.
  2. cj≤Ljc_j \le L_j but the slot was in use. Lines L6 to L8 do nothing, and line L9 rebuilds with ∣Sj∣≤cj≤Lj|S_j| \le c_j \le L_j. Lemma 1 applies.
  3. cj>Ljc_j > L_j and ∣Sj∣≤Lj/2|S_j| \le L_j/2. Line L9 rebuilds with ∣Sj∣≤Lj|S_j| \le L_j, and Lemma 1 applies.
  4. cj>Ljc_j > L_j and ∣Sj∣>Lj/2|S_j| > L_j/2. Before line L4, cj≤Ljc_j \le L_j, so now cj=Lj+1c_j = L_j + 1 and ∣Sj∣≤Lj+1≤2Lj|S_j| \le L_j + 1 \le 2L_j. Line L7 doubles LjL_j, so ∣Sj∣≤Lj|S_j| \le L_j for the new limit. If line L8 calls REHASH-ALL, Lemma 2 applies to SS, which contains xx since line L4. Otherwise ∑i2Li2≤512M\sum_i 2L_i^2 \le 512M, which is (D4), and line L9 rebuilds with ∣Sj∣≤Lj|S_j| \le L_j, so Lemma 1 applies.

In cases 2 to 4, the other buckets and LiL_i do not change, so their invariants still hold.

Effective delete of x∈Sx \in S. If line L12 calls REHASH-ALL, Lemma 2 gives the invariants for S∖{x}S \setminus \{x\}. Otherwise (D5) holds as above. By (D1) and (D2), x∈Sjx \in S_j for j=h(x)j = h(x), and Tj[hj(x)]=xT_j[h_j(x)] = x. Line L13 sets this slot to NIL and removes xx from SjS_j, so the stored keys are again exactly SjS_j, and hjh_j stays one-to-one on the smaller set: (D1) and (D2) hold. ∣Sj∣|S_j| goes down and cjc_j and LjL_j do not change, so (D3) and (D4) hold. □\square

Corollary (the size of MM). In each state, M≤2∣S∣+1M \le 2|S| + 1.

Proof. Let n0n_0 be the size of SS at the last REHASH-ALL. Since then, there were u≤⌈M/2⌉u \le \lceil M/2 \rceil effective updates by (D5), each changing ∣S∣|S| by 1, so ∣S∣≥n0−⌈M/2⌉|S| \ge n_0 - \lceil M/2 \rceil. If n0=0n_0 = 0, M=1≤2∣S∣+1M = 1 \le 2|S| + 1. Otherwise M=n0M = n_0, and ∣S∣≥n0−⌈n0/2⌉=⌊n0/2⌋≥(n0−1)/2|S| \ge n_0 - \lceil n_0/2 \rceil = \lfloor n_0/2 \rfloor \ge (n_0 - 1)/2, which gives M≤2∣S∣+1M \le 2|S| + 1. □\square

Back to Dynamic perfect hashing