BIT201 Data Structure and Algorithms

Data Structure and AlgorithmsUnit 1010 min read

Hashing & Collision Resolution: Hash Tables, Probing, and Real-World Hashing

Unit 10 of Data Structure and Algorithms introduces hashing—a technique to map keys to array indices for O(1) average-time lookups—while covering collision types, probing methods (linear, quadratic, double), and collision resolution (chaining vs. open addressing). It includes worked examples, comparisons, and real-worl

TAKEAWAYS

  • Hashing converts keys into array indices using a hash function to achieve constant-time access.
  • Collisions occur when two keys hash to the same index; probing (linear, quadratic, double) and chaining resolve them.
  • Open addressing (probing) vs. chaining (linked lists) trade off memory for speed.
  • Load factor (α = n/m) determines performance: higher α → more collisions.
  • Double hashing uses two hash functions to reduce clustering in quadratic probing.
  • Real-world systems (e.g., Khalti’s payment routing, Daraz’s inventory lookup) rely on hashing for efficiency.

1. Introduction to Hashing

Hashing is a mapping technique that converts a key into an index in a fixed-size array (hash table) for fast retrieval. The hash function maps keys to indices , where is the table size.

Why Hashing?

  • O(1) average-time for search/insert/delete (vs. O(n) for linear search).
  • Efficient storage for dictionaries, databases, and caches.
  • Used in eSewa’s transaction verification (hashing user IDs to transaction records) and NEPSE’s stock symbol lookup (hashing ticker symbols to stock data).

Key Terms

  • Hash Table: Array + hash function.
  • Hash Function: Deterministic mapping (e.g., ).
  • Bucket/Slot: Array index where a key is stored.
  • Load Factor (α): , where = entries, = table size.
    • Optimal α: 0.5–0.7 (beyond this, collisions rise sharply).

2. Hash Functions

A good hash function:

  • Distributes keys uniformly (minimizes collisions).
  • Computes quickly (e.g., modulo, polynomial rolling hash).

Common Hash Functions

Method Formula Example (key = "abc", m = 11)
Division Method
Multiplication (golden ratio)
Universal Hashing Randomized function for security Used in cryptographic hashing

Example: Hash the keys ["apple", "banana", "cherry"] into a table of size 5 using .

flowchart TD
    A["apple"] -->|"h('apple') = 5 mod 5 = 0"| B["Bucket 0"]
    C["banana"] -->|"h('banana') = 6 mod 5 = 1"| D["Bucket 1"]
    E["cherry"] -->|"h('cherry') = 7 mod 5 = 2"| F["Bucket 2"]

State after insertion:

Bucket 0: apple
Bucket 1: banana
Bucket 2: cherry

3. Collisions and Resolution

When two keys hash to the same index, a collision occurs. Resolution methods:

A. Chaining (Separate Chaining)

  • Each bucket holds a linked list of colliding keys.
  • Pros: Simple, handles many collisions.
  • Cons: Extra memory for pointers.

Example: Insert ["cat", "dog", "bat"] into a table of size 3 with .

flowchart TD
    A["Bucket 0"] --> B["cat"]
    A --> C["bat"]
    D["Bucket 1"] --> E["dog"]

State:

Bucket 0: cat → bat
Bucket 1: dog

B. Open Addressing (Probing)

  • No linked lists; find next empty slot via probing.
  • Probing Methods:
    1. Linear Probing: .
    2. Quadratic Probing: .
    3. Double Hashing: .

Example: Insert ["apple", "banana", "cherry"] into a size-5 table with linear probing.

  1. Insert "apple" → Bucket 0.
  2. Insert "banana" → Bucket 1.
  3. Insert "cherry" → Bucket 2.
  4. Insert "date" → .
  5. Insert "elderberry" → (collision with "cherry").
    • Probe: → Bucket 3.

State:

Bucket 0: apple
Bucket 1: banana
Bucket 2: cherry
Bucket 3: elderberry
Bucket 4: date

4. Quadratic Probing

Reduces clustering (unlike linear probing). Formula:

Example: Insert ["grape", "kiwi", "lemon"] into a size-5 table with .

  1. "grape" → Bucket 0.
  2. "kiwi" → Bucket 1.
  3. "lemon" → .
  4. Insert "mango" → .
  5. Insert "apple" → (collision).
    • Probe: (occupied), (occupied), → primary clustering (stuck in a loop).

Fix: Use double hashing to avoid clustering.


5. Double Hashing

Combines two hash functions to reduce clustering: where is coprime with .

Example: , . Insert ["pear", "orange"] into a size-5 table.

  1. "pear" → , .
    • Bucket 0.
  2. "orange" → , .
    • Bucket 1.
  3. Insert "plum" → , .
    • Bucket 2.
  4. Insert "quince" → , .
    • Bucket 3.
  5. Insert "apple" → (collision).
    • Probe: (occupied), (occupied), → Bucket 4.

State:

Bucket 0: pear
Bucket 1: orange
Bucket 2: plum
Bucket 3: quince
Bucket 4: apple

6. Comparison Table: Collision Resolution Methods

Method Pros Cons Best For
Chaining Simple, handles many collisions Extra memory for pointers High collision probability
Linear Probing No extra memory Clustering, poor performance Low collision probability
Quadratic Probing Avoids clustering Still may cluster Medium collision probability
Double Hashing No clustering Complex, slower High collision probability

7. Real-World Applications

In the Real World

  1. eSewa’s Transaction Lookup

    • Idea: Hashing maps user IDs to transaction records for O(1) access.
    • How: When a user requests a transaction history, eSewa uses a hash table to retrieve records instantly.
    • Example: If a user’s ID is 12345, the hash function computes an index to fetch their transactions without scanning all records.
  2. NEPSE’s Stock Symbol Indexing

    • Idea: Hashing maps stock symbols (e.g., "NCL", "NTC") to stock data.
    • How: Traders query symbols like h("NCL") to get real-time prices in milliseconds.
    • Example: A trader checks h("NCL") to find Nestle Nepal’s current share price.
  3. Khalti’s Payment Routing

    • Idea: Hashing routes payment requests to merchant accounts.
    • How: The payment ID (e.g., PAY_12345) is hashed to locate the merchant’s record.
    • Example: When a user pays via Khalti, the system uses hashing to verify the merchant’s details in microseconds.

8. Worked Example: Bank Loan Interest Calculation

Scenario: A bank uses hashing to store loan records (key = loan ID, value = interest rate).

  • Hash Table Size: 1000.
  • Hash Function: .
  • Loans: ["LOAN_101", "LOAN_202", "LOAN_303"] with rates 5%, 6%, 7%.

Insertion:

  1. .
  2. .
  3. .

State:

Bucket 101: LOAN_101 (5%)
Bucket 202: LOAN_202 (6%)
Bucket 303: LOAN_303 (7%)

Query: Find interest for LOAN_202.

  • Compute .
  • Retrieve rate: 6%.

Collision Handling: If two loans hash to the same bucket (e.g., LOAN_1001 and LOAN_2001 both → 1), use chaining or linear probing.


9. Time Complexity

Operation Average Case Worst Case (Chaining) Worst Case (Open Addressing)
Search/Insert O(1) O(n) O(n)
Delete O(1) O(n) O(n)

Note: Worst case occurs when all keys collide (e.g., ).


10. Exam Tip

  • Focus on:
    1. Definitions: Hash function, collision, load factor.
    2. Probing Methods: Draw linear/quadratic/double hashing steps.
    3. Chaining vs. Open Addressing: Compare trade-offs.
    4. Real-World Tie-Ins: Link to eSewa/Khalti/NEPSE examples.
    5. Worked Examples: Always show state after each insertion/query.
  • Common Pitfalls:
    • Forgetting to handle collisions in open addressing.
    • Misapplying quadratic probing (e.g., using instead of ).
    • Assuming O(1) always holds (worst case is O(n)).
  • Formula to Memorize:
    • Load factor: .
    • Quadratic probing: .

11. Code Example: Hash Table with Chaining (Python)

class HashTable:
    def __init__(self, size):
        self.size = size
        self.table = [[] for _ in range(size)]

    def hash(self, key):
        return key % self.size

    def insert(self, key, value):
        index = self.hash(key)
        bucket = self.table[index]
        for i, (k, v) in enumerate(bucket):
            if k == key:
                bucket[i] = (key, value)  # Update
                return
        bucket.append((key, value))  # Insert new

    def search(self, key):
        index = self.hash(key)
        bucket = self.table[index]
        for k, v in bucket:
            if k == key:
                return v
        return None

# Trace: Insert ("apple", 10), ("banana", 20), ("apple", 30)
ht = HashTable(3)
ht.insert("apple", 10)  # Bucket 1: [("apple", 10)]
ht.insert("banana", 20) # Bucket 2: [("banana", 20)]
ht.insert("apple", 30)  # Updates to [("apple", 30)]
print(ht.search("apple"))  # Output: 30

Output Table:

Step Operation Bucket State
1 Insert("apple",10) Bucket 1: [("apple",10)]
2 Insert("banana",20) Bucket 2: [("banana",20)]
3 Insert("apple",30) Bucket 1: [("apple",30)]

12. Summary Diagram: Hashing Workflow

flowchart TD
    A["Key"] --> B["Hash Function"]
    B --> C["Index = h(key) mod m"]
    C --> D["Check Bucket"]
    D -->|"Empty"| E["Store Key-Value"]
    D -->|"Collision"| F["Resolve (Chaining/Probing)"]
    F --> G["Store Key-Value"]
    G --> H["Return Value"]

Based on the TU BIT syllabus for Data Structure and Algorithms (BIT201), unit 10.

Discussion

Loading…