Proofs: Hopscotch hashing
These are the human proofs of Hopscotch hashing, at full length.
Worst-case and expected cost of hopscotch hashing sketch
This proof shows: time.worst, time.expected.
Worst case of search and delete. The loops of lines L18 and L22 run times, and each pass tests one bit and makes at most one key comparison. So each operation takes time, when a hash value and a key comparison cost . The bound is reached: put keys with the same home in a table with ; a search for another key with home compares it with all keys.
Worst case of HOP-PLACE. By Lemma 3 of the correctness proof, the loop of line L7 makes at most passes, and the loop of lines L9 to L15 makes at most passes, since falls by at least 1 at each pass and the loop stops when . A pass looks at the bitmaps for . On the word RAM with , the test of line L11 for one bitmap is , and the smallest such is the index of the lowest set bit of that word, both word operations. So a pass takes time, and HOP-PLACE takes time. One attempt of RESIZE to slots makes calls of HOP-PLACE, each in ; a sharper count follows from the expected case below.
Lemma 4 (the same full slots as linear probing). Consider a sequence of inserts of distinct keys into an empty hopscotch table with slots, with no delete and no failed HOP-PLACE, and the same sequence in a linear-probing table with slots and the same homes. After each insert, the two tables have the same set of full slots.
Proof. By induction on the number of inserts. Before an insert of , the two sets of full slots are equal, so line L7 stops at the slot where linear probing puts : the first empty slot at or after . Each pass of the loop L9 to L15 fills the empty slot and empties a slot that was full; and line L16 fills the last empty slot of the chain. So the net change of the set of full slots is that slot becomes full, as in linear probing.
Expected cost of HOP-PLACE. Assume the homes of the keys are independent and uniform on the slots, and that keys are in the table, inserted as in Lemma 4. Then the distance found by line L7 is the number of probes of an unsuccessful search of linear probing in a table with keys, minus 1. Knuth (Volume 3, Section 6.4, the analysis of Algorithm L) shows that the expected number of such probes is about for large and fixed ; see the analysis of linear probing. We quote this result and do not prove it here. The loop L9 to L15 makes at most passes of time each, so the expected time of HOP-PLACE is
This bounds an insert that does not trigger a resize. The probability that HOP-PLACE fails depends on and ; Herlihy, Shavit and Tzafrir study it by experiment, and this proof does not bound it. With deletes, Lemma 4 no longer holds as stated, because hopscotch hashing empties the slot of a deleted key while linear probing must keep a tombstone or move keys back.
Hopscotch hashing keeps its neighbourhood invariant and implements a dictionary full
This proof shows: correctness, termination.
Write and for the distance of slot from slot . Assume . Then for each slot , the slots for are distinct slots, so bit of names one slot. The invariant (P1), (P2), (P3) is the one on the page, and is the set of pairs in full slots.
Lemma 1 (one neighbourhood suffices). Assume (P1) and (P2). A key is in the table if and only if, with , there is a such that bit of is 1 and slot holds .
Proof. If is in slot , let . By (P1), , and . Slot holds a key with home , so bit of is 1 by (P2). The converse is clear.
So the loops of lines L2 and L3, L18 and L19, and L22 and L23 find if and only if is in the table, and they find its only slot, by (P3).
Lemma 2 (one displacement). Assume (P1) and (P2), let slot be empty, and let , , and be as in lines L11 and L13, so , , , bit of is 1 and . After line L14, (P1) and (P2) hold, slot is empty, and is the same.
Proof. By (P2), slot holds a key with home . Line L14 moves the pair of to slot , which was empty, and empties slot ; so does not change. Now , so (P1) holds for , and it holds for every other key, which did not move.
For (P2), only the bits that refer to slots and can change their truth. Slot is and slot is . For bucket : slot no longer holds a key with home , and line L14 clears bit ; slot now holds , with home , and line L14 sets bit . For a bucket : before the move, slot held , whose home is not , and slot was empty, so all the bits of that name or were 0. After the move, slot is empty and slot holds , whose home is not , so these bits must still be 0, and they are, since line L14 changes only .
Lemma 3 (HOP-PLACE). Assume (P1), (P2) and (P3), and that is not in the table. Then HOP-PLACE ends after at most passes of each of its loops. If it returns OK, (P1) and (P2) hold, and the full slots hold the old pairs and , each once. If it returns FAIL, (P1), (P2) and (P3) hold and is the same as before the call.
Proof. The loop of line L7 raises at each pass and stops at , so it ends after at most passes. If it stops with , all slots are full, and line L8 returns FAIL with nothing changed. Otherwise slot is empty, with .
For the loop of lines L9 to L15, use the loop invariant: (P1) and (P2) hold, is the set of the old pairs, , and slot is empty. It holds at the first test of line L9 by the paragraph above. In a pass, , and line L10 sets , which is empty. If line L12 returns FAIL, nothing has changed in this pass, and the claim of the lemma for FAIL follows from the loop invariant; (P3) holds since and the set of full slots did not change. Otherwise Lemma 2 applies, so after line L14, (P1) and (P2) hold, is the same, and slot is empty, where
Line L15 sets . Since and , we get , and slot is empty. So the loop invariant holds again. As falls by at least 1 at each pass and stays at least 1, the loop makes at most passes.
When the loop ends, and slot is empty. By (P2), every bit that names slot is 0, in particular bit of . Line L16 puts in slot and sets that bit. Then (P1) holds for , as , and (P2) holds: the only bits that name slot belong to the buckets with , and of these only bucket is the home of the key in slot . The full slots hold the old pairs, which are distinct, and , and was absent.
Theorem. Start from the empty table, with , and apply any sequence of HOP-INSERT, HOP-SEARCH and HOP-DELETE in which every call to RESIZE returns. After each call, (P1), (P2) and (P3) hold, is the map that the same sequence gives on an abstract dictionary, and each search returns the value of its key in , or NIL. HOP-SEARCH, HOP-DELETE and each call to HOP-PLACE end after at most , and passes of their loops.
Proof. Base. No slot is full and all bits are 0, so the invariant holds and is empty.
Search. By Lemma 1, lines L17 to L20 return the value of if is a key and NIL otherwise, after at most passes, and change nothing.
Delete. By Lemma 1, lines L21 to L23 find the slot of if is a key. Line L24 empties it, clears bit of and lowers . (P1) holds for the other keys, which did not move. For (P2), only bits that name slot change truth; the one that was 1 was bit of the home of , which is now 0, and the others were 0 and stay 0. (P3) holds, and loses exactly the pair of . If is not a key, nothing changes.
Insert. By Lemma 1, lines L1 to L3 find if it is a key, and then only its value changes. Otherwise is absent. Each call of HOP-PLACE at line L4 that returns FAIL leaves the invariant and , by Lemma 3. Then RESIZE saves the list of the pairs, which are pairs with distinct keys. Each attempt of lines L27 and L28 starts from an empty table of slots, in which the invariant holds with empty, and calls HOP-PLACE on the pairs of , none of which is in the table yet, since the keys of are distinct. By Lemma 3, after an attempt in which all calls return OK, the table holds exactly the pairs of and (P1) and (P2) hold; was not changed and equals , so (P3) holds. Since , the condition holds for each new size. So when RESIZE returns, the invariant holds and is as before. When HOP-PLACE at line L4 returns OK, Lemma 3 shows that the table holds the old pairs and , and line L5 raises , so (P3) holds and gains exactly .
By induction on the number of calls, the claims hold after each call.
The theorem assumes that RESIZE returns. It does when, for some size , all the placements succeed; this fails forever when more than keys have the same value , as the page explains. The Python version stops with an error in that case and when the table would pass slots, which keeps the invariant, since a failed HOP-PLACE does.