Hashing
Time limit: 60 minutes Total marks: 50 Instructions: Answer all questions. Show all working, including probe sequences, tables, and reasoning. No hints are provided.
Q1. Open addressing with mixed operations (12 marks)
You have a hash table of size using linear probing with hash function .
Insert the following keys in order: .
(a) Show the final table (index 0–10) with the key stored at each index. (6 marks)
(b) Now delete key using a tombstone marker, then search for key . State the exact sequence of indices probed during the search for , and explain why the tombstone is required for correctness rather than simply blanking the slot. (6 marks)
Q2. Double hashing probe sequence (10 marks)
A table of size uses double hashing with
Key is inserted into a table where slots and are already occupied (all others empty).
(a) Compute the full probe sequence for inserting until an empty slot is found. Give the index of every probe. (7 marks)
(b) Explain why is deliberately never allowed to be , and why choosing prime helps double hashing. (3 marks)
Q3. Load factor, resizing and amortized cost (12 marks)
A chaining hash table starts with capacity and doubles capacity whenever the load factor would exceed after an insertion. Each resize rehashes all existing elements. Insertions are done one at a time starting from empty.
(a) State the capacity of the table immediately after inserting the and elements. (5 marks)
(b) Consider inserting elements into an initially empty table (capacity 4, doubling policy). Show that the total work done, including all rehashing, is , and hence the amortized cost per insertion is . Give the geometric-series argument explicitly. (7 marks)
Q4. Universal hashing guarantee (8 marks)
Let be a universal family of hash functions mapping a universe into .
(a) State the defining property of a universal family (the collision-probability bound). (2 marks)
(b) We choose uniformly at random from and insert distinct keys into a table of size using chaining. Let be the number of keys that collide with a fixed key (i.e. hash to the same slot as , excluding itself). Prove that , and hence the expected search time for is . (6 marks)
Q5. Applications — design problem (8 marks)
You are given an array of integers and a target . You must return all distinct unordered pairs with .
(a) Design an average-time algorithm using a hash structure. Give clear pseudocode and explain how you avoid (i) duplicate pairs and (ii) reusing the same element twice when . (5 marks)
(b) State the average and worst-case time complexity, and explain the single realistic condition under which the worst case occurs. (3 marks)
Answer keyMark scheme & solutions
Q1 (12 marks)
(a) . Insert order :
- (empty) → index 0
- (empty) → index 8
- (occupied by 22) → probe 1 → index 1
- (occ), 1 (occ 33) → probe 2 → index 2
- (occ), 9 (empty) → index 9
- (occ),9(occ),10 (empty) → index 10
Final table: (6 marks — 1 per correct placement)
| idx | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| key | 22 | 33 | 44 | – | – | – | – | – | 8 | 19 | 30 |
(b) Delete at index 1 → replace with tombstone (DEL marker). (2)
Search for : .
- probe index 0 → key 22 ≠ 44, continue
- probe index 1 → tombstone, must continue (do NOT stop)
- probe index 2 → key 44 found. ✓
Probe indices: 0, 1, 2. (2)
Why tombstone (2): If we simply blanked slot 1, the search for would hit an empty slot at index 1 and conclude is absent — but lives at index 2 because it was placed there by probing past the old key . The tombstone preserves the probe chain: it tells search "keep going," while telling insert "you may reuse this slot."
Q2 (10 marks)
(a) . , → start at 9. . , → step .
Probe sequence: :
- : 9 → occupied
- : 11 → empty → insert at 11
Wait — check: slot 9 occupied (given), slot 11 empty → inserted at 11.
Probe indices computed: 9, 11. Since 11 is empty, insertion stops. (Full sequence would be 9, 11, 0, 2, 4, 6, 8, 10, 12, 1, 3, 5, 7.)
Marks: correct (2), correct (3), correct final placement at 11 (2).
(b) (3) must never be because a step size of makes the probe sequence stay on the same slot forever — an infinite loop that never explores the table. Choosing prime ensures for every allowed step, so the probe sequence visits all slots before repeating — guaranteeing an empty slot is found if one exists.
Q3 (12 marks)
(a) Policy: after insertion, if , double . Threshold at is (since ok, ). So resize triggers when count exceeds .
- After 1st insert: →
- After 3rd insert: (not ) →
- After 4th insert: → double →
- After 7th insert: → double →
- After 13th insert: → double →
Answers: 4, 4, 8, 16, 32. (1 each)
(b) (7) Ordinary insertions each cost : insertions cost total. Resizes occur when the table doubles: at capacities A resize at capacity rehashes elements (cost ). The capacities at which resizes happen form a geometric sequence up to the final capacity . Total rehashing work: Explicitly, , and the largest term , so the sum . Total work . Amortized per insertion . ∎
Marks: inserts O(n) (1), resize cost Θ(C) (2), geometric sum bounded by O(n) (3), conclude amortized O(1) (1).
Q4 (8 marks)
(a) (2) is universal if for all distinct :
(b) (6) Fix key . Define indicator if , for each of the other keys . Then . By universality . (2) By linearity of expectation: (2) With : (1) Expected search time for = time to scan its bucket . (1) ∎
Q5 (8 marks)
(a) (5) Use a hash set of seen values plus a result set of pairs.
found = set() # values seen so far
pairs = set() # canonical (min,max) tuples
for x in array:
need = T - x
if need in found:
pairs.add( (min(x,need), max(x,need)) )
found.add(x)
return pairs
- Avoiding duplicate pairs (2): store each pair in canonical order
(min,max)in a set, so(2,5)and(5,2)collapse and repeated occurrences don't re-add. - Avoiding element reuse when (2): because we only test
need in foundbefore adding the currentx, a value pairs with another only if a previous was already inserted — i.e. two distinct array positions exist. A lone with won't match itself. (1 for clean pseudocode)
(b) (3) Average time (each lookup/insert expected). Worst case — occurs only when hashing degrades so that essentially all keys collide into one bucket (adversarial keys / bad hash function), making each set operation . With a good/universal hash and random data this is negligible.
[
{"claim":"Q1: linear probing final placements for 22,8,33,44,19,30 mod 11",
"code":"m=11\nkeys=[22,8,33,44,19,30]\ntab=[None]*m\nfor k in keys:\n i=k%m\n while tab[i] is not None:\n i=(i+1)%m\n tab[i]=k\nresult = tab==[22,33,44,None,None,None,None,None,8,19,30]"},
{"claim":"Q2: double hashing 100 into m=13 with slots 9,3 occupied lands at 11",
"code":"m=13\nocc={9,3}\nh1=100%13\nstep=1+(100%11)\ni=0\nwhile ((h1+i*step)%m) in occ:\n i+=1\nslot=(h1+i*step)%m\nresult = (h1==9 and step==2 and slot==11)"},
{"claim":"Q3: capacities after inserts 1,3,4,7,13 with double-when->0.75 policy",
"code":"def caps(n):\n C=4;cnt=0;out={}\n for _ in range(n):\n cnt+=1\n if cnt/C>0.75:\n C*=2\n out[cnt]=C\n return out\nd=caps(13)\nresult = (d[1]==4 and d[3]==4 and d[4]==8 and d[7]==16 and d[13]==32)"},
{"claim":"Q3: total rehash cost 4+8+16+...+2n is O(n); sum < 8n for n up to 1000",
"code":"import math\nok=True\nfor n in range(4,1001):\n total=0;C=4\n while C<2*n:\n total+=C\n C*=2\n ok = ok and (total < 8*n)\nresult = ok"},
{"claim":"Q4: universal hashing expected collisions with m=n is < 1",
"code":"n=symbols('n',positive=True)\nexpected=(n-1)/n\nresult = simplify(expected<1)==True or bool((expected.subs(n,1000))<1)"}
]