Showing posts with label Hash Table. Show all posts
Showing posts with label Hash Table. Show all posts

Hash Table Chaining in C++.

One of the key challenges in hash table implementation is handling collisions, where two keys hash to the same index. Chaining and Open Addressing are two techniques that help us to manage these collisions gracefully. In this tutorial, you will learn how to handle collisions using the Chaining method in detail. 


What is Hash Table Chaining?

In chaining collision, each bucket or index of the hash table contains a linked list or another data structure. When a collision occurs, the colliding elements are appended to the linked list at the corresponding index. Chaining allows multiple elements with the same hash value to coexist at the same index.

Chaining in Hash Table

Advantages of Chaining in Hash Table.

  • Chaining handles collisions effectively, ensuring that no data is lost.
  • Chaining facilitates dynamic resizing, accommodating varying numbers of elements.
  • Implementing chaining is straightforward, making it an attractive option for hash table design.


Disadvantages of Chaining in Hash Table.

  • Each element requires additional memory for the linked list pointers, contributing to increased memory overhead.
  • In scenarios with high collision rates, the performance of the linked lists might degrade, impacting overall hash table performance.

Implementation of Chaining.

Instead of storing multiple elements at the same index, chaining involves maintaining a linked list (or another data structure) at each index to store all colliding elements.

Hash Function: The hash function calculates the hash value for each key. The hash value determines the index at which the key's corresponding value will be stored.

Index Calculation: Calculate the hash value using the hash function: index = hash(key) % table_size.

Collision Occurs: If two keys hash to the same index, a collision occurs. In chaining, this is not a problem.

Linked List Handling: Each index in the hash table contains a linked list. If the linked list is empty, insert the key-value pair at the corresponding index and if the linked list is not empty, append the key-value pair to the end of the list.

Searching: When searching for a key, calculate its hash and navigate the linked list at the corresponding index. If the key is found in the linked list, return its associated value and if the linked list is empty or the key is not found, the key is not in the hash table.

Deleting: To delete a key, calculate its hash and search for it in the linked list. If found, remove the node from the linked list. If the linked list becomes empty after deletion, update the index in the hash table.


C++ code to implement Chaining in Hash Table:

// C++ code of chaining in hash table
#include <iostream>
#include <list>
using namespace std;

// HashTable class with Chaining
class HashTable {
private:
    int table_size;  // Size of the hash table
    // Array of linked lists
    list<pair<int, int>>* table;  

    // Hash function to determine the index
    int hash(int key) {
        return key % table_size;
    }

public:
    // Constructor to initialize the hash table
    HashTable(int size) {
        table_size = size;
        table = new list<pair<int, int>>[table_size];
    }

    // Function to insert a key-value pair into the hash table
    void insert(int key, int value) {
        int index = hash(key);
        table[index].push_back(make_pair(key, value));
    }

    // Function to search for a key and return its value
    int search(int key) {
        int index = hash(key);
        for (auto& it : table[index]) {
            if (it.first == key) {
                return it.second;  // Key found, return its value
            }
        }
        return -1;  // Key not found
    }

    // Function to delete a key from the hash table
    void remove(int key) {
        int index = hash(key);
        for (auto it = table[index].begin(); it != table[index].end(); ++it) {
            if (it->first == key) {
                table[index].erase(it);  // Remove the key-value pair
                break;
            }
        }
    }

    // Function to display the hash table
    void display() {
        for (int i = 0; i < table_size; ++i) {
            cout << "[" << i << "] -> ";
            for (const auto& it : table[i]) {
                cout << "(" << it.first << ", " << it.second << ") -> ";
            }
            cout << "nullptr\n";
        }
    }
};

// Main function for testing
int main() {
    // Create a hash table with a size of 5
    HashTable hashTable(5);

    // Insert key-value pairs
    hashTable.insert(25, 250);
    hashTable.insert(14, 140);
    hashTable.insert(7, 70);

    // Display the hash table
    cout << "Hash Table after Insertion:\n";
    hashTable.display();

    // Search for a key
    int searchResult = hashTable.search(14);
    if (searchResult != -1) {
        cout << "Value for key 14: " << searchResult << "\n";
    } else {
        cout << "Key 14 not found.\n";
    }

    // Remove a key
    hashTable.remove(14);
    cout << "\nHash Table after Removal:\n";
    hashTable.display();

    return 0;
}
Output:
Hash Table after Insertion:
[0] -> (25, 250) -> nullptr
[1] -> nullptr
[2] -> (7, 70) -> nullptr
[3] -> nullptr
[4] -> (14, 140) -> nullptr
Value for key 14: 140

Hash Table after Removal:
[0] -> (25, 250) -> nullptr
[1] -> nullptr
[2] -> (7, 70) -> nullptr
[3] -> nullptr
[4] -> nullptr

This C++ code defines a HashTable class with chaining. It includes functions for inserting, searching, removing keys, and displaying the hash table. The main function demonstrates the usage of the hash table with a few key-value pairs.

Program to Find Union of Two Array.

Given two integer arrays, arr1[] and arr2[], the task is to find the union of the two arrays. The output should contain only distinct elements and the order of the elements in the output may vary. 

Union of Array.
The union of two arrays is defined as the set of distinct elements that are present in either of the arrays. The resulting array must contain only unique elements, and the order of elements in the output may vary.

Example:
Input: arr1[] = {1, 2, 3, 4, 5}, arr2[] = {3, 4, 5, 6, 7}
Output: Union[] = {1, 2, 3, 4, 5, 6, 7}

Input: arr1[] = {4, 7, 9, 1}, arr2[] = {1, 5}
Output: Union[] = {1, 4, 5, 7, 9}

There are multiple methods to find the union of two arrays and here we are going to discuss a few of them in detail with code. 

Approach 1: Brute Force.

The brute force approach involves iterating through both arrays and checking for each element if it's already in the result array. If not, add it to the result. This method ensures that the result contains only unique elements.

Step-by-step Algorithm to find the Union of Two Arrays:
  • STEP 1: Initialize an empty result[] array.
  • STEP 2: Iterate through arr1[] and arr2[].
  • STEP 3: For each element, check if it is not already in the result array.
  • STEP 4: If not, add it to the result[] array.
  • STEP 5: The result array is the union of arr1[] and arr2[].

C++ Code Implementation.

// C++ code to find Union of two arrays
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

// function to find union of two arrays
vector<int> findUnionOfArrays(vector<int>& arr1, vector<int>& arr2) {
    vector<int> result;
    
    //insert the elements which are not present in result array
    for (int num : arr1) {
        if (find(result.begin(), result.end(), num) == result.end()) {
            result.push_back(num);
        }
    }

    for (int num : arr2) {
        if (find(result.begin(), result.end(), num) == result.end()) {
            result.push_back(num);
        }
    }

    return result;
}


int main() {
    vector<int> arr1 = {4, 9, 5, 1, 0};
    vector<int> arr2 = {9, 4, 9, 8, 4, 1};

    vector<int> result = findUnionOfArrays(arr1, arr2);

    // Print the Union of Two Arrays 
    for (int i = 0; i < result.size(); ++i) {
        cout << result[i] << " ";
    }

    return 0;
}
Output:
4 9 5 1 0 8 
  • Time Complexity: O(m * n) where m and n are the sizes of arr1 and arr2.
  • Space Complexity: O(k) where k is the size of the result array.

Approach 2: Using Hash Map.

In this approach, we are using a Hash Map to keep track of unique elements.

Step-by-step to find the Union of Two Arrays.
  • STEP 1: Create a hash map to count occurrences of elements.
  • STEP 2: Iterate through arr1 and arr2, updating counts in the hash map.
  • STEP 3: Extract keys from the hash map to get the result.

C++ Code Implementation.

// C++ code to find Union of two arrays
#include <iostream>
#include <vector>
#include <unordered_map>
using namespace std;

// function to find union of two arrays
vector<int> findUnionUsingHashMap(vector<int>& arr1, vector<int>& arr2) {
    unordered_map<int, int> countMap;

    for (int num : arr1) {
        countMap[num]++;
    }

    for (int num : arr2) {
        countMap[num]++;
    }

    vector<int> result;
    // store all unique elements to resultant array
    for (const auto& entry : countMap) {
        result.push_back(entry.first);
    }

    return result;
}

int main() {
    vector<int> arr1 = {4, 9, 5, 1, 0};
    vector<int> arr2 = {9, 4, 9, 8, 4, 1};

    vector<int> result = findUnionUsingHashMap(arr1, arr2);

    // Print the Union of Two Arrays 
    for (int i = 0; i < result.size(); ++i) {
        cout << result[i] << " ";
    }

    return 0;
}
Output:
8 0 1 5 9 4 
  • Time Complexity: O(m + n) due to hash map operations.
  • Space Complexity: O(k) where k is the size of the result array.

How To Resolve Collison in Hash Table in C++.

In hash table implementations, collisions occur when two different keys hash to the same index in the array. To handle collisions, various strategies are employed to ensure efficient storage and retrieval of data. In this article, we'll explore some common methods to resolve collisions and provide working examples in C++.


Collision Resolution Methods in Hash Table.

There are multiple ways to resolve collision in Hash Table (Hash Map) but here we are going to learn about three popular collision resolution methods mentioned below:

Now let's discuss each of these methods one by one in detail and use them in Hash Table Implementation.


Method 1: Chaining (Separate Chaining)

Separate Chaining is a popular collision resolution technique used in hash table implementations. It involves maintaining a linked list at each hash table index to handle collisions.


How does Separate Chaining Work?

It involves four important steps to implement the Chaining method to resolve collision:

1. Initialization: Create an array of linked lists, where each list represents a bucket in the hash table.


2. Insertion: When inserting a key-value pair, compute the hash of the key and determine the index in the array. Add the key-value pair to the linked list at that index.


3. Search: To retrieve a value for a given key, calculate the hash to find the index, and then search the linked list at that index for the key.


4. Collision Handling: When multiple keys hash to the same index (collision), they are simply added to the same linked list at that index.


Implementation of Separate Chaining in C++.

// C++ code for the implementation of Chaining in Hash Table
#include <iostream>
#include <list>
#include <utility>

class HashTable {
private:
    static const int tableSize = 10;
    std::list<std::pair<int, int>> table[tableSize];

    int hashFunction(int key) {
        return key % tableSize;
    }

public:
    void insert(int key, int value) {
        int index = hashFunction(key);
        table[index].emplace_back(key, value);
    }

    int get(int key) {
        int index = hashFunction(key);
        for (const auto& pair : table[index]) {
            if (pair.first == key) {
                return pair.second;
            }
        }
        return -1; // Key not found
    }
};

int main() {
    HashTable hashTable;

    hashTable.insert(5, 50);
    hashTable.insert(15, 150);
    hashTable.insert(25, 250);
 
    std::cout << "Value at key 15: " << hashTable.get(15) << std::endl;

    return 0;
}
Output:
Value at key 15: 150

Method 1: Open Addressing.

Open Addressing is another widely used collision resolution technique in hash table implementations. Unlike Separate Chaining, Open Addressing aims to resolve collisions by placing the collided element directly into the hash table itself.

How Open Addressing Works?

It involves four important steps to implement the Open Address method to resolve collision:

1. Initialization: Create an array (hash table) to hold the key-value pairs. Initially, all slots are empty.

2. Insertion: When inserting a key-value pair, compute the hash of the key and determine the index in the array. If the slot is occupied, use a probing sequence (linear, quadratic, etc.) to find the next available slot.

3. Search: To retrieve a value for a given key, calculate the hash to find the index, and then use the same probing sequence to search for the key.

4. Collision Handling: When a collision occurs, Open Addressing attempts to find the next available slot within the array to place the collided element.

Implementation of Open Addressing in C++.

// C++ code to implement Open Addression in Hash Map
#include <iostream>
#include <vector>
using namespace std;

class HashTable {
private:
    static const int tableSize = 10;
    vector<pair<int, int>> table;

    int hashFunction(int key) {
        return key % tableSize;
    }

    int linearProbing(int index, int attempt) {
        return (index + attempt) % tableSize;
    }

public:
    HashTable() : table(tableSize, std::make_pair(-1, -1)) {}

    void insert(int key, int value) {
        int index = hashFunction(key);
        int attempt = 0;

        while (table[index].first != -1) {
            index = linearProbing(index, ++attempt);
        }

        table[index] = make_pair(key, value);
    }

    int get(int key) {
        int index = hashFunction(key);
        int attempt = 0;

        while (table[index].first != -1) {
            if (table[index].first == key) {
                return table[index].second;
            }
            index = linearProbing(index, ++attempt);
        }

        return -1; // Key not found
    }
};

int main() {
    HashTable hashTable;

    hashTable.insert(5, 50);
    hashTable.insert(15, 150);
    hashTable.insert(25, 250);

    cout << "Value at key 15: " << hashTable.get(15) << endl;

    return 0;
}
Output:
Value at key 15: 150

Method 3: Robin Hood Hashing.

Robin Hood Hashing is a variant of Open Addressing, a collision resolution technique used in hash tables. It focuses on distributing collided elements more evenly within the table to achieve better performance.

How Robin Hood Hashing Works?

It involves four important steps to implement the Robin Hood Hashing method to resolve collision:

1. Initialization: Create an array (hash table) to hold the key-value pairs. Initially, all slots are empty.

2. Insertion: When inserting a key-value pair, calculate the hash of the key and determine the index in the array. If the slot is occupied, compare the distances between the desired index and the current index for both the existing element and the new element. If the new element's distance is smaller, swap the elements and continue probing.

3. Search: To retrieve a value for a given key, calculate the hash to find the index, and then use the same probing sequence to search for the key.

4. Collision Handling: Robin Hood Hashing aims to minimize the difference in the probing distances, ensuring a more balanced distribution of elements.

Implementation of Robin Hood Hashing in C++.

// C++ code to implement Robin Hood Hashing
#include <iostream>
#include <vector>
using namespace std;

class HashTable {
private:
    static const int tableSize = 10;
    vector<pair<int, int>> table;

    int hashFunction(int key) {
        return key % tableSize;
    }

public:
    HashTable() : table(tableSize, make_pair(-1, -1)) {}

    void insert(int key, int value) {
        int index = hashFunction(key);
        int distance = 0;

        while (table[index].first != -1) {
            if (distance > tableSize - 1) {
                break; // Avoid infinite loop
            }

            if (distance > index - hashFunction(table[index].first)) {
                swap(key, table[index].first);
                swap(value, table[index].second);
                distance = index - hashFunction(key);
            }

            index = (index + 1) % tableSize;
            distance++;
        }

        table[index] = make_pair(key, value);
    }

    int get(int key) {
        int index = hashFunction(key);
        int distance = 0;

        while (table[index].first != -1) {
            if (table[index].first == key) {
                return table[index].second;
            }
            index = (index + 1) % tableSize;
            distance++;
        }

        return -1; // Key not found
    }
};

int main() {
    HashTable hashTable;

    hashTable.insert(5, 50);
    hashTable.insert(15, 150);
    hashTable.insert(25, 250);

    cout << "Value at key 15: " << hashTable.get(15) << endl;

    return 0;
}
Output:
Value at key 15: 150

Conclusion:

Collision resolution is a critical aspect of hash table implementations. Chaining is a popular method using linked lists to store colliding elements. This ensures efficient storage and retrieval while keeping collision-related complexities under control. In more advanced implementations, other methods like open addressing and Robin Hood hashing are explored to further optimize hash table performance.

Program to Count Number of Good Pair.

Given an integer array num of size n, return the count of possible good pairs. A pair of elements is called a good pair if (num[i] == num[j]) and i < j.

Example:
Example 1:
Input: num[] = {2, 1, 3, 2, 2, 3};
Output: 4

Explanation: 
There are four good pairs (0, 3), (0, 4), (2, 5) and (3, 4)

Example 2:
Input: num[] = {3, 3, 3, 3}
Output: 6

Approach 1: Brute Force Solution.

Take a count variable and initialize it with 0. Run two nested for loops for each element of the array, pick each element one by one and then compare it with the remaining element and then keep on increasing the count variable whenever the given condition is satisfied. 

C++ Solution Code:
/*C++ code for count number of good pairs*/
#include<bits/stdc++.h>
using namespace std;

int goodPairs(vector<int> &num){
    int count = 0;

    for(int i = 0; i < num.size(); i++){
        for(int j = i+1; j < num.size(); j++){
            if(num[i] == num[j])
              count++;
        }
    }
    return count;
}

int main(){
    vector<int> num = {2, 1, 3, 2, 2, 3};

    int count = goodPairs(num);
    cout<<"Number of Good Pairs: "<<count;

    return 0;   
}
Output:
Number of Good Pairs: 4
  • Time Complexity: Running two nested loops for n elements takes O(n^2) time complexity.
  • Space Complexity: No extra space is required in the above solution so space complexity is O(1).


Approach 2: Using Hash Map.

You can optimize the previous solution by using a hash map to get the count of each unique element of the array. 
Given num = [2, 1, 3, 2, 2, 3]

Structure of map:
Key      Value(count)
 2          3 (Value 2 appears 3 times)
 1          1 (Value 1 appears 1 times)
 3          2 (Value 3 appears 2 times)       

When you have n number of similar values then it can create [n*(n-1)/2] number of unique good pairs. 
[3, 3, 3, 3]
Number of Good Pairs = 4*(4-1)/2 = 6

Similarly, you can calculate the number of unique good pairs for each value of the map and sum them up to get the total number of good pairs possible from all array elements.
Key      Value(count)      Good Pairs
 2          3            [3*(3-1)/2 = 3] 
 1          1            [1*(1-1)/2 = 0]
 3          2            [2*(2-1)/2 = 1]  
Total number of Good Pairs = 3+0+1 = 4     

C++ Solution Code:
/*C++ code for count number of good pairs(Optimized)*/
#include<bits/stdc++.h>
using namespace std;

int goodPairs(vector<int> &num){
    int count = 0;
    unordered_map<int, int> mp;

    //Storing count of each unique element
    for(int i = 0; i < num.size(); i++){
        mp[num[i]]++;
    }

    //counting good pairs
    for(auto i:mp){
        int n = i.second;
        count += (n*(n-1))/2;
    }
    return count;
}

int main(){
    vector<int> num = {2, 1, 3, 2, 2, 3};

    int count = goodPairs(num);
    cout<<"Number of Good Pairs: "<<count;

    return 0;   
}
Output:
Number of Good Pairs: 4
  • Time Complexity: We run two separate loops one for storing the count in the map and the next loop for counting good pairs so the average time complexity will be O(n).
  • Space Complexity: We are using one map to store the count of elements so space complexity will be O(n).
Next:

Count number of elements smaller than current number.

Given an array num of size n, for each element of num count the number of elements of the array that are smaller than the current element. It means for each element num[i] you need to count the number of elements such that num[j] < num[i] and j != i. Return the counts in an array.

Example:
Example 1:
Input: num[] = {3, 2, 5, 1, 7};
Output: {2, 1, 3, 0, 4}

Explanation: 
Number of Elements smaller than num[0] = 3 are (1 and 2) = 2 
Number of Elements smaller than num[1] = 2 are (1) = 1
Number of Elements smaller than num[2] = 5 are (1, 2 and 3) = 3
Number of Elements smaller than num[3] = 1 are () = 0
Number of Elements smaller than num[4] = 7 are (1, 2, 3 and 5) = 4

Example 2:
Input: num[] = {3, 3, 3, 3}
Output: {0, 0, 0, 0}

Example 3:
Input: num[] = {3, 2, 2, 5, 1, 7}
Output: {3, 1, 1, 4, 0, 5}

The question is very simple, you just have to get the counts of smaller elements for each and store them one by one in an array or vector and return them as your output. This question can be solved by multiple approaches let's discuss them here.
Note: We are using a C++ vector in our solution instead of an array because vectors are dynamic in nature and provide more functionality. (alert-passed)  

Approach 1: Brute Force Solution.

You can run two nested loops, the outer loop will pick elements of the array one by one, and the inner loop will traverse the whole array each time to check and count the elements that satisfy the given condition. Each time the inner loop terminates store the count value into a new resulting array and return this array at the end. 
 
C++ Solution code:
/*C++ program for how many number of elements smaller
 than current number.*/
#include<iostream>
#include<vector>
using namespace std;

vector<int> smallerNumber(vector<int> &num){
    vector<int> ans;
    int count;

    for(int i = 0; i < num.size(); i++){
        count = 0;
        for(int j = 0; j < num.size(); j++){
            if(num[i] > num[j])
               count++;
        }
        ans.push_back(count);
    }
    return ans;
}

int main(){
    vector<int> num = {3, 2, 2, 5, 1, 7};
    vector<int> result = smallerNumber(num);

    for(int i = 0; i < result.size(); i++)
       cout<<result[i]<<" ";

    return 0;   
}
Output:
3 1 1 4 0 5
  • Time Complexity: Running two nested loops from 0 to n will take n x n units of time so the time complexity will be O(n^2).
  • Space Complexity: No extra space is required to perform any operation except the answer vector that is used to store the counts but we cannot consider this for calculating space complexity so overall space complexity will be O(1).


Approach 2: Using Hash Map and Sorting.

The above approach is solving the problem in O(n^2) time complexity but we can optimize the solution and can bring it to O(nlogn) time complexity by following the below algorithm.
Given num = [3, 2, 2, 5, 1, 7]

Step 1: Make a copy of the given array and sort it in ascending order.
copy = [1, 2, 2, 3, 5, 7]

Step 2: Store the values in a hash map corresponding to their index in reverse order. A map can store only unique values and storing values in reverse will help you to get the count of smaller values by their index.
mp[copy[5]] = mp[7] = 5
mp[copy[4]] = mp[5] = 4
mp[copy[3]] = mp[3] = 3
mp[copy[2]] = mp[2] = 1(overwritten 2 to 1)  
mp[copy[0]] = mp[1] = 0

Step 3: Store the values back in the num array.
num[0] = mp[num[0]] = mp[3] = 3
num[1] = mp[num[1]] = mp[2] = 1
num[2] = mp[num[2]] = mp[2] = 1
num[3] = mp[num[3]] = mp[5] = 4
num[4] = mp[num[4]] = mp[1] = 0
num[5] = mp[num[5]] = mp[7] = 5

Step 4: Return the num array as our answer.
num = [3, 1, 1, 4, 0, 5]

C++ Solution code:
/*C++ Optimized program for how many number of elements 
smaller than current number.*/
#include<bits/stdc++.h>
using namespace std;

vector<int> smallerNumber(vector<int> &num){
    unordered_map<int, int> mp;

    //make a copy vector
    vector<int> copy = num;
    //sort the copy vector in ascending order
    sort(copy.begin(), copy.end());

    //store elements corresponding to their index
    for(int i = num.size() - 1; i >= 0; i--){
        mp[copy[i]] = i;
    }
    
    //store map value back to num array
    for(int i = 0; i < num.size(); i++){
        num[i] = mp[num[i]];
    }
    return num;
}

int main(){
    vector<int> num = {3, 2, 2, 5, 1, 7};

    vector<int> result = smallerNumber(num);

    for(int i = 0; i < result.size(); i++)
       cout<<result[i]<<" ";

    return 0;   
}
Output:
3 1 1 4 0 5
  • Time Complexity: Sorting the copied array will take O(nlogn) time, and running two separate loops for n elements will take 2O(n) time so the overall time complexity of our solution is O(nlogn).
  • Space Complexity: As we are using a new copy vector and unordered map for storing the element so overall space complexity is O(n). 

DON'T MISS

Tech News
© all rights reserved
made with by AlgoLesson