Data Structures And AlgorithmsUnit 78 min read
Hashing: Functions, Collisions, and Resolution Techniques
Unit 7 of Data Structures And Algorithms covers hashing—how to map keys to array indices, design hash functions, resolve collisions (open/closed addressing), and analyze performance. Includes real-world examples from eSewa, Daraz, and Ncell, plus step-by-step traces of insertion/deletion in hash tables.
What is Hashing?
Hashing is a technique to store and retrieve data efficiently by mapping keys to indices in an array (called a hash table). The goal is O(1) average-time operations (insertion, deletion, search).
Key Terms
- Hash Function (h(k)): A function that converts a key into an index.
Example:
h(key) = key % table_size - Hash Table: An array where data is stored at indices computed by the hash function.
- Collision: When two keys hash to the same index.
- Load Factor (λ): Ratio of filled slots to total slots. High λ → more collisions.
In the Real World
- eSewa (Nepal) uses hashing to store user transaction IDs. When you pay a bill, eSewa’s backend hashes your transaction ID to locate the record in milliseconds, avoiding slow database searches.
- Daraz’s Order Queue uses a hash table to track order statuses. Your order ID (e.g.,
ORD12345) is hashed to find its current state (processing/shipped/delivered) in constant time. - Ncell’s Caller ID Lookup hashes phone numbers to quickly fetch subscriber names (e.g.,
98XXXXXXXX→ index in a hash table storing names).
Hash Functions
A good hash function must:
- Distribute keys uniformly (minimize collisions).
- Be deterministic (same key → same index).
- Be fast (computable in O(1)).
Common Hash Functions
| Function | Formula | Example | Pros/Cons |
|---|---|---|---|
| Division Method | h(k) = k % m (m = table size) |
h(62) = 62 % 10 = 2 |
Simple, but poor for non-random keys. |
| Multiplication Method | h(k) = floor(m * (k*A mod 1)) |
A = (√5 - 1)/2 ≈ 0.618 |
Better distribution for integers. |
| Universal Hashing | Randomized: h(k) = ((a*k + b) % p) % m |
a=5, b=7, p=11, m=10 → h(62) = 3 |
Reduces clustering attacks. |
| String Hashing | Polynomial rolling hash: h(s) = (s[0]*p^(n-1) + ... + s[n-1]) % m |
h("abc") = (97*26² + 98*26 + 99) % 10 = 7 |
Used in compilers/databases. |
Collision Resolution Techniques
When two keys collide, we resolve it using:
- Open Addressing (Closed Hashing): Find the next empty slot.
- Separate Chaining (Open Hashing): Store colliding keys in a linked list/array at the same index.
1. Open Addressing
Methods:
- Linear Probing: Check next slot sequentially.
h(k) = (h(k) + i) % m(wherei = 1, 2, 3...). - Quadratic Probing: Jump in squares to reduce clustering.
h(k) = (h(k) + i²) % m. - Double Hashing: Use a second hash function to determine step size.
h(k, i) = (h1(k) + i * h2(k)) % m.
Example: Linear Probing
Insert keys [62, 37, 36, 44, 67, 91, 107] into a table of size 10.
Hash function: h(k) = k % 10.
Final Table State:
Index: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
Value: - | 91| 62| - | 44| - | 36| 37| 67| 107
Trace of Insertion Steps:
| Step | Key | Hash Index | Probe Path | Final Slot |
|---|---|---|---|---|
| 1 | 62 | 2 | 2 | 2 |
| 2 | 37 | 7 | 7 | 7 |
| 3 | 36 | 6 | 6 | 6 |
| 4 | 44 | 4 | 4 | 4 |
| 5 | 67 | 7 | 7 → 8 | 8 |
| 6 | 91 | 1 | 1 | 1 |
| 7 | 107 | 7 | 7 → 8 → 9 | 9 |
2. Separate Chaining
Store colliding keys in a linked list/array at the same index.
Example: Chaining with Linked Lists
Insert the same keys [62, 37, 36, 44, 67, 91, 107] into a table of size 10.
Final Table State:
Index: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
Value: - | 91 | 62 | - | 44 | - | 36 | 37 → 67 | 107 | -
Advantages/Disadvantages:
| Technique | Pros | Cons |
|---|---|---|
| Open Addressing | No extra memory for pointers. | Clustering degrades performance. |
| Separate Chaining | Handles high load factors well. | Extra memory for linked lists/arrays. |
Performance Analysis
- Average Case: O(1) for insert/search/delete (with good hash function).
- Worst Case: O(n) if all keys collide (hash function fails).
- Load Factor (λ): Optimal λ ≈ 0.7 for open addressing, higher for chaining.
Amortized Time Complexity:
| Operation | Open Addressing | Separate Chaining |
|---|---|---|
| Insertion | O(1) | O(1 + α) |
| Search | O(1) | O(1 + α) |
| Deletion | O(1) | O(1 + α) |
Where α = λ (load factor).
Real-World Worked Example: Ncell’s Caller ID
Ncell stores 10 million subscriber records. To fetch a name in <10ms, they:
- Use a hash table of size 1,000,000 (load factor λ ≈ 0.8).
- Hash phone numbers with
h(phone) = (phone % 1,000,000). - Resolve collisions via separate chaining (linked lists).
- Average lookup time: O(1 + 0.8) ≈ constant time.
Trace for Phone 9812345678:
- Hash:
9812345678 % 1,000,000 = 123456. - Check
Table[123456]→ linked list with 2 entries (due to λ=0.8). - Search list linearly (max 2 comparisons).
- Return name: "Ramesh Shrestha".
Exam Tip
- Define hashing clearly: "A technique to map keys to array indices using a hash function."
- Collision resolution: Always explain how the method works (e.g., "Linear probing checks slots sequentially").
- Draw tables: Show before/after states for insertions/deletions.
- Compare methods: Use a table for open vs. closed addressing.
- Practical touch: Relate to eSewa/Daraz (e.g., "How would you design a hash table for 1 million orders?").
- Code snippets: Write pseudocode for probing (e.g.,
while (table[h] != null && table[h].key != key) h = (h + 1) % size;).
Practice Questions
- Insert
[11, 22, 33, 44, 55]into a table of size 5 using quadratic probing. - Compare linear vs. double hashing for collision resolution.
- Why does universal hashing reduce clustering attacks?
- Design a hash table for a bank’s loan records (keys: loan IDs, values: customer details). Choose a resolution method and justify.
# Python: Linear Probing Implementation
class HashTable:
def __init__(self, size):
self.size = size
self.table = [None] * size
def insert(self, key):
index = key % self.size
while self.table[index] is not None:
index = (index + 1) % self.size # Linear probe
self.table[index] = key
# Trace for inserting [62, 37, 36, 44, 67, 91, 107] (size=10)
ht = HashTable(10)
for key in [62, 37, 36, 44, 67, 91, 107]:
ht.insert(key)
print(ht.table) # Output: [-1, 91, 62, -1, 44, -1, 36, 37, 67, 107]
Based on the TU BCA syllabus for Data Structures And Algorithms (CACS201), unit 7.
Discussion
Loading…