Algorithm Search⌘K

Proofs: Swiss table

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

The Swiss table keeps its invariant, and each find gives the right answer full

This proof shows: correctness, termination.

We use the notation of the page: GG groups of WW slots, GG a power of two, m=GWm = GW, and for a key kk the probe sequence gi(k)=(h1(k)+i(i+1)/2) mod Gg_i(k) = (h_1(k) + i(i+1)/2) \bmod G. A slot is full when its control byte is in {0,…,127}\{0, \ldots, 127\}, and free when it is EMPTY or DELETED. For a full slot ss, grp(s)=⌊s/W⌋\text{grp}(s) = \lfloor s / W \rfloor. The invariant is:

(S1) A slot is full exactly when its control byte is in {0,…,127}\{0, \ldots, 127\}, and then the byte is h2(key[s])h_2(\text{key}[s]). No key is in two full slots. nn and dd are the numbers of full and of DELETED slots.

(S2) For each full slot ss with key kk, let jj be the least index with gj(k)=grp(s)g_j(k) = \text{grp}(s). Then none of the groups g0(k),…,gj−1(k)g_0(k), \ldots, g_{j-1}(k) holds an EMPTY slot.

(S3) n+d≤MaxFill(m)≤m−1n + d \le \text{MaxFill}(m) \le m - 1.

Lemma 1 (coverage). For each key kk, the groups g0(k),…,gG−1(k)g_0(k), \ldots, g_{G-1}(k) are all GG groups, each once. So the least index jj in (S2) exists and is less than GG.

Proof. This is Lemma 1 of the quadratic probing proof, with G=2pG = 2^p in place of mm: the triangular numbers i(i+1)/2i(i+1)/2 for 0≤i<2p0 \le i < 2^p are distinct modulo 2p2^p. □\square

Lemma 2 (the variable gg). In SWISS-FIND and in lines L10 to L12 of SWISS-INSERT, the group gg in the iteration with counter ii is gi(k)g_i(k).

Proof. By induction on ii. For i=0i = 0, g=h1 mod G=g0(k)g = h_1 \bmod G = g_0(k). Line L6 or L12 adds i+1i + 1 to gg after it adds 1 to ii, and i(i+1)/2+(i+1)=(i+1)(i+2)/2i(i+1)/2 + (i + 1) = (i+1)(i+2)/2. □\square

Lemma 3 (find). If (S1) to (S3) hold, SWISS-FIND(T,k)(T, k) returns the slot of kk if kk is in TT, and NIL otherwise, and it looks at fewer than G+1G + 1 groups. It changes nothing.

Proof. Line L4 returns a slot ss only if key[s]=k\text{key}[s] = k and its byte is h2(k)h_2(k), which is a full slot by (S1), so a returned slot holds kk. If kk is not in TT, line L4 never returns, and the find returns NIL at line L5 or does not end. By (S3) some slot is EMPTY, so some group has an EMPTY slot. By Lemmas 1 and 2, iteration i<Gi < G reaches that group, and line L5 returns there. So the find ends within GG iterations.

Now let kk be in the full slot ss, and let jj be as in (S2). For i<ji < j, the group gi(k)g_i(k) is not grp(s)\text{grp}(s), and kk is in no other slot by (S1), so line L4 does not return. By (S2) the group has no EMPTY slot, so line L5 does not return. In iteration jj, the slot ss is in group gj(k)g_j(k) and its byte is h2(k)h_2(k) by (S1), so s∈Ms \in M and line L4 returns ss. As j<Gj < G, at most GG groups are read. □\square

Lemma 4 (an EMPTY group stays EMPTY). Say that a group is open when it holds an EMPTY slot. Between two rebuilds, a group that is not open never becomes open.

Proof. Only two lines write EMPTY: line L22, in a rebuild, and line L18. Line L18 writes EMPTY into a slot of a group that is open at that time. So no line makes a group open that was not open before. □\square

Lemma 5 (insert). If (S1) to (S3) hold, then after SWISS-INSERT(T,k,v)(T, k, v) they hold again, D(k)=vD(k) = v, and the other pairs do not change.

Proof. By Lemma 3, line L7 finds the slot of kk if it is present, and line L8 then changes only its value, which keeps (S1) to (S3).

Otherwise kk is not in TT. If line L9 calls REBUILD, assume for now that REBUILD gives a table with the same pairs in which (S1) to (S3) hold and n+d+1≤MaxFill(m)n + d + 1 \le \text{MaxFill}(m) (Lemma 7). So at line L10, n+d+1≤MaxFill(m)≤m−1n + d + 1 \le \text{MaxFill}(m) \le m - 1, and as n+d<mn + d < m some slot is free. By Lemmas 1 and 2, the loop of lines L11 and L12 ends at the first group gj(k)g_j(k) with a free slot, after j<Gj < G steps. Line L13 takes a free slot ss of that group, line L14 updates dd if ss was DELETED, and line L15 makes ss full with byte h2(k)h_2(k) and adds 1 to nn.

(S1) holds: kk was not present, so it is now in one slot, and the counts are right. (S3) holds: n+dn + d grows by 1 if ss was EMPTY and stays the same if ss was DELETED, and before the insert n+d+1≤MaxFill(m)n + d + 1 \le \text{MaxFill}(m). (S2) for kk: the least index with gi(k)=grp(s)g_i(k) = \text{grp}(s) is jj, because the groups g0(k),…,gG−1(k)g_0(k), \ldots, g_{G-1}(k) are distinct. The groups g0(k),…,gj−1(k)g_0(k), \ldots, g_{j-1}(k) had no free slot, so no EMPTY slot, and line L15 does not change them. (S2) for the other keys: they do not move, and the only change is that a free slot becomes full, which does not add an EMPTY slot to any group. □\square

Lemma 6 (erase). If (S1) to (S3) hold, then after SWISS-ERASE(T,k)(T, k) they hold again, kk is not in TT, and the other pairs do not change. The result is true exactly when kk was in TT.

Proof. By Lemma 3, line L16 finds the slot ss of kk, or NIL, and then line L17 returns false and nothing changes. Otherwise lines L18 to L20 make ss free and lower nn by 1, so kk is no longer in TT, and (S1) holds with the new counts. n+dn + d does not grow, so (S3) holds.

For (S2), take another key k′k' at a full slot s′s', with its index j′j'. If line L19 runs, no slot becomes EMPTY, so (S2) for k′k' still holds. If line L18 runs, the group a=grp(s)a = \text{grp}(s) was open before the erase. If aa were one of g0(k′),…,gj′−1(k′)g_0(k'), \ldots, g_{j'-1}(k'), the old (S2) for k′k' would say that aa is not open, which is false. So aa is not among these groups, and they do not change. (S2) for k′k' holds. □\square

Lemma 7 (rebuild). REBUILD makes a table with the same pairs in which (S1) to (S3) hold, d=0d = 0, and n+1≤MaxFill(m)/2n + 1 \le \text{MaxFill}(m)/2, where mm is the new number of slots. It ends.

Proof. Line L21 doubles G′G' until 2(n+1)≤MaxFill(G′W)2(n + 1) \le \text{MaxFill}(G'W). This ends, because MaxFill(G′W)≥7G′W/8−1\text{MaxFill}(G'W) \ge 7G'W/8 - 1 grows without a bound. The new table has all slots EMPTY, so (S1) to (S3) hold with no pairs. Line L22 puts the old pairs in one at a time by lines L10 to L15. They are distinct keys, so each one is not yet present, and before each put n+d+1≤nold≤MaxFill/2−1n + d + 1 \le n_{\text{old}} \le \text{MaxFill}/2 - 1, so the argument of Lemma 5 applies to each put and keeps (S1) to (S3). At the end the table holds the old pairs, d=0d = 0, and n+1≤MaxFill(m)/2n + 1 \le \text{MaxFill}(m)/2. □\square

Theorem. Start from a table of EMPTY slots and apply SWISS-INSERT, SWISS-ERASE and SWISS-FIND in any order. Then (S1) to (S3) hold after each operation, the pairs in the table are the map DD given by the operations, and each find returns the slot of its key or NIL as it should. Each operation ends.

Proof. The EMPTY table has n=d=0n = d = 0 and no full slots, so (S1) to (S3) hold, and DD is empty. Lemmas 5 and 6 keep the invariant and change the pairs as the dictionary asks, and Lemma 3 gives the answer of each find. By induction on the number of operations, the claim holds after each one. Each find reads at most GG groups, each insert or erase runs one find, at most GG steps of lines L11 and L12, and at most one rebuild, and the rebuild ends by Lemma 7. ■\blacksquare

Remark. Lemma 4 is not used in the proofs above, but it explains line L18: an open group was open at each moment since the last rebuild, so no find for a present key has ever moved past it, and a slot there can be made EMPTY. Abseil's rule (count the slots that are not EMPTY next to the erased slot, and write EMPTY if the count is less than WW) is the same idea for unaligned windows of WW slots: if no window of WW slots without an EMPTY slot covers the erased slot, no probe has moved past it.

Back to Swiss table