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 as 1 for the hash and the bucket index, plus 1 for each pair that the scan of bucket examines. The cost of a call of RESIZE is : one for each new list and one for each pair moved.
Claim 1 (worst case). Some sets of keys make an operation cost .
Proof. The scan of a bucket examines at most all pairs, so each operation costs without the resize, and a resize from line L6 costs since . For the lower bound, fix and , and start with buckets where , so that inserts cause no resize. If the key universe has at least keys, then by the pigeonhole principle some keys have the same bucket . Insert these keys. Their bucket holds all pairs, and a search for the key that is last in that list examines pairs.
Claim 2 (expected cost without the resize). Let be drawn from a family such that, for each two distinct keys , . Let the table hold a set of keys that does not depend on the choice of . Then the expected cost of a search, an insert (before line L6) or a delete of a key is at most , where .
Proof. For with , let if , and else. Then . The scan examines only pairs of bucket , which are itself (if ) and the keys with . So the cost is at most . By linearity of expectation, the expected cost is at most .
By (H4), after each operation, and during an insert before line L6, so . Hence each operation costs in expectation, without the resize.
Claim 3 (the resizes are amortized). Start with an empty table of buckets, and apply operations. Then the total cost of all calls of RESIZE is less than .
Proof. A resize happens only at line L6, when , and it doubles . If there is no resize, the total is 0. Else let be the number of resizes. The sizes before them are . The resize at size makes lists and moves pairs, so it costs . Before the last resize the table holds keys, and each key needs its own insert, so . The total cost is at most .
So the total cost of operations is the sum of their costs without the resize, which has expectation at most by Claim 2 (each operation sees a set that does not depend on when the sequence is fixed in advance), plus less than for the resizes. The expected total is , and each operation costs expected amortized time.
The condition that the keys do not depend on is necessary. An adversary who sees , 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 for the bucket of key in a table of buckets, and for the set of pairs that the lists of hold. The invariant is the one of the page:
- (H1) each pair of is in the list ;
- (H2) no key occurs in two pairs of ;
- (H3) is the total length of the lists;
- (H4) .
Theorem. Start from an empty table with buckets, and apply any sequence of HASH-INSERT, HASH-SEARCH and HASH-DELETE. Then after each operation (H1) to (H4) hold, and 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 , so (H1) to (H4) hold, and is the empty map. We show that each operation keeps the invariant and changes as the dictionary operation does. Fix an operation on key , and let be the index that line L1, L7 or L11 computes.
Step 1 (the scan is complete). Assume (H1). If some pair of has key , then by (H1) this pair is in . So a scan of alone finds each pair with key , and if the scan finds none, then is not a key of .
Step 2 (search). Lines L8 and L9 scan and return the value of the first pair with key . By Step 1 and (H2), this is the only pair with key in the table, so the value is . If there is no such pair in , then by Step 1 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 , line L3 replaces by and returns. No pair moves, no key changes, and and do not change, so (H1) to (H4) hold. By (H2), this was the only pair with key , so is now the old map with mapped to , as the dictionary asks.
Step 4 (insert of a new key). If the scan finds no pair with key , then by Step 1 is not a key. Line L4 appends to , which keeps (H1). Since was not a key, (H2) holds. Line L5 adds 1 to and the lists grow by one pair, so (H3) holds. Before the operation , so now . If , line L6 does nothing and (H4) holds. Else , and line L6 calls RESIZE. By Step 6 below, RESIZE keeps (H1) to (H3) for the new size and does not change , and after it since . So (H4) holds. The map is now the old map plus .
Step 5 (delete). Lines L12 and L13 scan . If they find a pair with key , they remove it and subtract 1 from . By Step 1 and (H2), this was the only pair with key , so is now the old map without . The other pairs do not move, so (H1) and (H2) hold, (H3) holds because both sides drop by 1, and (H4) holds because decreased and did not change. If they find no pair, is not a key, nothing changes, and the map is the old map without , as the dictionary asks.
Step 6 (resize). Assume (H2) and (H3) for the old table, and let . Line L14 makes empty lists. The loop of lines L15 and L16 visits each pair of each old list exactly once and appends it to . So after the loop, the new lists hold exactly the pairs of , each one once, and each pair is in . Line L17 makes and the fields of . Hence (H1) holds for the new size, is the same set of pairs, so (H2) holds, and the total length of the lists is the old total, which is , so (H3) holds.
By induction on the number of operations, using Steps 2 to 5, the invariant holds after each operation and follows the abstract dictionary.