Algorithm Search⌘K

Proofs: Hash table

These are the human proofs of Hash table, at full length.

Expected and amortized cost of the chained hash table full

This proof shows: time.expected, time.worst.

Count the cost of an operation on key kk as 1 for the hash and the bucket index, plus 1 for each pair that the scan of bucket bm(k)b_m(k) examines. The cost of a call of RESIZE(T,m′)(T, m') is m′+nm' + n: one for each new list and one for each pair moved.

Claim 1 (worst case). Some sets of keys make an operation cost Θ(n)\Theta(n).

Proof. The scan of a bucket examines at most all nn pairs, so each operation costs O(n)O(n) without the resize, and a resize from line L6 costs 2m+n=O(n)2m + n = O(n) since m<nm < n. For the lower bound, fix hh and mm, and start with mm buckets where m≥nm \ge n, so that nn inserts cause no resize. If the key universe has at least (n−1)m+1(n - 1) m + 1 keys, then by the pigeonhole principle some nn keys have the same bucket bmb_m. Insert these nn keys. Their bucket holds all nn pairs, and a search for the key that is last in that list examines nn pairs. □\square

Claim 2 (expected cost without the resize). Let hh be drawn from a family H\mathcal{H} such that, for each two distinct keys x≠yx \ne y, Pr⁡h∈H[bm(x)=bm(y)]≤1/m\Pr_{h \in \mathcal{H}}[b_m(x) = b_m(y)] \le 1/m. Let the table hold a set SS of nn keys that does not depend on the choice of hh. Then the expected cost of a search, an insert (before line L6) or a delete of a key kk is at most 2+α2 + \alpha, where α=n/m\alpha = n/m.

Proof. For y∈Sy \in S with y≠ky \ne k, let Xy=1X_y = 1 if bm(y)=bm(k)b_m(y) = b_m(k), and Xy=0X_y = 0 else. Then E[Xy]=Pr⁡[bm(y)=bm(k)]≤1/mE[X_y] = \Pr[b_m(y) = b_m(k)] \le 1/m. The scan examines only pairs of bucket bm(k)b_m(k), which are kk itself (if k∈Sk \in S) and the keys yy with Xy=1X_y = 1. So the cost is at most 1+1+∑y∈S,y≠kXy1 + 1 + \sum_{y \in S, y \ne k} X_y. By linearity of expectation, the expected cost is at most 2+∑y≠kE[Xy]≤2+n/m=2+α2 + \sum_{y \ne k} E[X_y] \le 2 + n/m = 2 + \alpha. □\square

By (H4), α≤1\alpha \le 1 after each operation, and during an insert n≤m+1n \le m + 1 before line L6, so α≤2\alpha \le 2. Hence each operation costs O(1)O(1) in expectation, without the resize.

Claim 3 (the resizes are amortized). Start with an empty table of m0≥1m_0 \ge 1 buckets, and apply ss operations. Then the total cost of all calls of RESIZE is less than 8s8s.

Proof. A resize happens only at line L6, when n=m+1n = m + 1, and it doubles mm. If there is no resize, the total is 0. Else let r+1r + 1 be the number of resizes. The sizes before them are m0,2m0,…,2rm0m_0, 2 m_0, \ldots, 2^{r} m_0. The resize at size m′=2jm0m' = 2^j m_0 makes 2m′2m' lists and moves n=m′+1n = m' + 1 pairs, so it costs 3m′+1≤4m′3m' + 1 \le 4m'. Before the last resize the table holds 2rm0+12^r m_0 + 1 keys, and each key needs its own insert, so s≥2rm0+1>2rm0s \ge 2^r m_0 + 1 > 2^r m_0. The total cost is at most ∑j=0r4⋅2jm0<4⋅2r+1m0=8⋅2rm0<8s\sum_{j=0}^{r} 4 \cdot 2^j m_0 < 4 \cdot 2^{r+1} m_0 = 8 \cdot 2^r m_0 < 8 s. □\square

So the total cost of ss operations is the sum of their costs without the resize, which has expectation at most (2+2)s=4s(2 + 2) s = 4s by Claim 2 (each operation sees a set that does not depend on hh when the sequence is fixed in advance), plus less than 8s8s for the resizes. The expected total is O(s)O(s), and each operation costs O(1)O(1) expected amortized time. ■\blacksquare

The condition that the keys do not depend on hh is necessary. An adversary who sees hh, or the order of the keys in an iteration, can pick keys with the same bucket, and then Claim 1 applies.

The chained hash table keeps its invariant and implements a dictionary full

This proof shows: correctness.

We write bm(k)=h(k) mod mb_m(k) = h(k) \bmod m for the bucket of key kk in a table of mm buckets, and D(T)D(T) for the set of pairs that the lists of TT hold. The invariant is the one of the page:

  • (H1) each pair (k,v)(k, v) of D(T)D(T) is in the list B[bm(k)]B[b_m(k)];
  • (H2) no key occurs in two pairs of D(T)D(T);
  • (H3) nn is the total length of the lists;
  • (H4) n≤mn \le m.

Theorem. Start from an empty table with m≥1m \ge 1 buckets, and apply any sequence of HASH-INSERT, HASH-SEARCH and HASH-DELETE. Then after each operation (H1) to (H4) hold, and D(T)D(T) is the map that the same sequence gives on an abstract dictionary. Each search returns the value of its key in that map, or NIL.

Proof. The empty table has no pair and n=0≤mn = 0 \le m, so (H1) to (H4) hold, and D(T)D(T) is the empty map. We show that each operation keeps the invariant and changes D(T)D(T) as the dictionary operation does. Fix an operation on key kk, and let i=bm(k)i = b_m(k) be the index that line L1, L7 or L11 computes.

Step 1 (the scan is complete). Assume (H1). If some pair of D(T)D(T) has key kk, then by (H1) this pair is in B[i]B[i]. So a scan of B[i]B[i] alone finds each pair with key kk, and if the scan finds none, then kk is not a key of D(T)D(T).

Step 2 (search). Lines L8 and L9 scan B[i]B[i] and return the value of the first pair with key kk. By Step 1 and (H2), this is the only pair with key kk in the table, so the value is D(T)(k)D(T)(k). If there is no such pair in B[i]B[i], then by Step 1 kk is not a key, and line L10 returns NIL. Search changes no field, so the invariant holds after it.

Step 3 (insert of a present key). If the scan of lines L2 and L3 finds a pair (k,v′)(k, v'), line L3 replaces v′v' by vv and returns. No pair moves, no key changes, and nn and mm do not change, so (H1) to (H4) hold. By (H2), this was the only pair with key kk, so D(T)D(T) is now the old map with kk mapped to vv, as the dictionary asks.

Step 4 (insert of a new key). If the scan finds no pair with key kk, then by Step 1 kk is not a key. Line L4 appends (k,v)(k, v) to B[i]=B[bm(k)]B[i] = B[b_m(k)], which keeps (H1). Since kk was not a key, (H2) holds. Line L5 adds 1 to nn and the lists grow by one pair, so (H3) holds. Before the operation n≤mn \le m, so now n≤m+1n \le m + 1. If n≤mn \le m, line L6 does nothing and (H4) holds. Else n=m+1n = m + 1, and line L6 calls RESIZE(T,2m)(T, 2m). By Step 6 below, RESIZE keeps (H1) to (H3) for the new size and does not change D(T)D(T), and after it n=m+1≤2mn = m + 1 \le 2m since m≥1m \ge 1. So (H4) holds. The map is now the old map plus (k,v)(k, v).

Step 5 (delete). Lines L12 and L13 scan B[i]B[i]. If they find a pair with key kk, they remove it and subtract 1 from nn. By Step 1 and (H2), this was the only pair with key kk, so D(T)D(T) is now the old map without kk. The other pairs do not move, so (H1) and (H2) hold, (H3) holds because both sides drop by 1, and (H4) holds because nn decreased and mm did not change. If they find no pair, kk is not a key, nothing changes, and the map is the old map without kk, as the dictionary asks.

Step 6 (resize). Assume (H2) and (H3) for the old table, and let m′≥1m' \ge 1. Line L14 makes m′m' empty lists. The loop of lines L15 and L16 visits each pair of each old list exactly once and appends it to B′[bm′(k)]B'[b_{m'}(k)]. So after the loop, the new lists hold exactly the pairs of D(T)D(T), each one once, and each pair (k,v)(k, v) is in B′[bm′(k)]B'[b_{m'}(k)]. Line L17 makes B′B' and m′m' the fields of TT. Hence (H1) holds for the new size, D(T)D(T) is the same set of pairs, so (H2) holds, and the total length of the lists is the old total, which is nn, so (H3) holds.

By induction on the number of operations, using Steps 2 to 5, the invariant holds after each operation and D(T)D(T) follows the abstract dictionary. ■\blacksquare

Back to Hash table