How to use unordered_map efficiently in C++

Pre-requisite: unordered_set,  unordered_map 

C++ provides std::unordered_set and std::unordered_map to be used as a hash set and hash map respectively. They perform insertion/deletion/access in constant average time. 

  1. However, the worst-case complexity is O(n2).
  2. The reason is that the unordered_map store’s key-value pair by taking the modulo of input value by a prime number and then stores it in a hash table.
  3. When the input data is big and input values are multiples of this prime number a lot of collisions take place and may cause the complexity of O(n2).
  4. Depending on the compiler the prime number maybe 107897 or 126271.

Example 1: If we insert multiples of the above two prime numbers and compute execution time. One of the prime numbers takes a much longer time than the other.

C++

filter_none

edit
close

play_arrow

link
brightness_4
code

// C++ program to determine worst case
// time complexity of an unordered_map
  
#include <bits/stdc++.h>
using namespace std;
using namespace std::chrono;
int N = 55000;
int prime1 = 107897;
int prime2 = 126271;
  
void insert(int prime)
{
  
    // Starting the clock
    auto start
        = high_resolution_clock::now();
  
    unordered_map<int, int> umap;
  
    // Inserting multiples of prime
    // number as key in the map
    for (int i = 1; i <= N; i++)
        umap[i * prime] = i;
  
    // Stopping the clock
    auto stop
        = high_resolution_clock::now();
  
    // Typecasting the time to
    // milliseconds
    auto duration
        = duration_cast<milliseconds>(
            stop - start);
  
    // Time in seconds
    cout << "for " << prime << " : "
         << duration.count() / 1000.0
         << " seconds "
         << endl;
}
  
// Driver code
int main()
{
    // Function call for prime 1
    insert(prime1);
  
    // Function call for prime 2
    insert(prime2);
}

chevron_right


Output:

for 107897 : 2.261 seconds 
for 126271 : 0.024 seconds

Clearly, for one of the prime numbers, the time complexity is O(n2).



The standard inbuilt hash function on which unordered_map works is similar to this:

C++

filter_none

edit
close

play_arrow

link
brightness_4
code

struct hash {
    size_t operator()(uint64_t x)
        const { return x; }
};

chevron_right


The above function can produce numerous collisions. The keys inserted in HashMap are not evenly distributed, and after inserting numerous prime multiples, further insertion causes the hash function to reallocate all previous keys to new slots hence making it slow. So, the idea is that we have to randomize the hash function.
The idea is to use a method so that the keys in our hashmap are evenly distributed. This will prevent collisions to take place. For this, we use Fibonacci numbers. The golden ratio related to the Fibonacci sequence (Phi = 1.618) has a property that it can subdivide any range evenly without looping back to the starting position.

We can create our own simple hash function. Below is the hash function:

C++

filter_none

edit
close

play_arrow

link
brightness_4
code

struct modified_hash {
    static uint64_t splitmix64(uint64_t x)
    {
  
        // 0x9e3779b97f4a7c15,
        // 0xbf58476d1ce4e5b9,
        // 0x94d049bb133111eb are numbers
        // that are obtained by dividing
        // high powers of two with Phi
        // (1.6180..) In this way the
        // value of x is modified
        // to evenly distribute
        // keys in hash table
        x += 0x9e3779b97f4a7c15;
        x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9;
        x = (x ^ (x >> 27)) * 0x94d049bb133111eb;
        return x ^ (x >> 31);
    }
  
    int operator()(uint64_t x) const
    {
        static const uint64_t random
            = steady_clock::now()
                  .time_since_epoch()
                  .count();
  
        // The above line generates a
        // random number using
        // high precision clock
        return splitmix64(
  
            // It returns final hash value
            x + random);
    }
};

chevron_right


Basically, the above hashing function generates random hash values to store keys. To know more about this please refer to this article Fibonacci hashing.

Example 2: Using the above hashing function, the program runs very quickly.

C++

filter_none

edit
close

play_arrow

link
brightness_4
code

// C++ program to determine worst case
// time complexity of an unordered_map
// using modified hash function
  
#include <bits/stdc++.h>
using namespace std;
using namespace std::chrono;
  
struct modified_hash {
  
    static uint64_t splitmix64(uint64_t x)
    {
        x += 0x9e3779b97f4a7c15;
        x = (x ^ (x >> 30))
            * 0xbf58476d1ce4e5b9;
        x = (x ^ (x >> 27))
            * 0x94d049bb133111eb;
        return x ^ (x >> 31);
    }
  
    int operator()(uint64_t x) const
    {
        static const uint64_t random
            = steady_clock::now()
                  .time_since_epoch()
                  .count();
        return splitmix64(x + random);
    }
};
  
int N = 55000;
int prime1 = 107897;
int prime2 = 126271;
  
// Function to insert in the hashMap
void insert(int prime)
{
    auto start = high_resolution_clock::now();
  
    // Third argument in initialisation
    // of unordered_map ensures that
    // the map uses the hash function
    unordered_map<int, int, modified_hash>
        umap;
  
    // Inserting multiples of prime
    // number as key in the map
    for (int i = 1; i <= N; i++)
        umap[i * prime] = i;
  
    auto stop
        = high_resolution_clock::now();
  
    auto duration
        = duration_cast<milliseconds>(
            stop - start);
  
    cout << "for " << prime << " : "
         << duration.count() / 1000.0
         << " seconds "
         << endl;
}
  
// Driver Code
int main()
{
    // Function call for prime 1
    insert(prime1);
  
    // Function call for prime 2
    insert(prime2);
}

chevron_right


Output:

for 107897 : 0.025 seconds 
for 126271 : 0.024 seconds

Attention reader! Don’t stop learning now. Get hold of all the important DSA concepts with the DSA Self Paced Course at a student-friendly price and become industry ready.




My Personal Notes arrow_drop_up


If you like GeeksforGeeks and would like to contribute, you can also write an article using contribute.geeksforgeeks.org or mail your article to contribute@geeksforgeeks.org. See your article appearing on the GeeksforGeeks main page and help other Geeks.

Please Improve this article if you find anything incorrect by clicking on the "Improve Article" button below.


Article Tags :
Practice Tags :


7


Please write to us at contribute@geeksforgeeks.org to report any issue with the above content.