Design an efficient data structure for given operations


In order to design efficient data structures for specific operations, the time and space complexity of the given operations for the data structure created is important. Looking into some basic operations and how they can be efficiently optimized −

  • insert() − Inserts an element to the data structure

    Dynamic Arrays, Hash Tables, Binary Search Trees and Balanced Search Trees like AVL Trees or Red-Black Trees are the most efficient choice of data structures providing O(1) complexity for insertions operation.

  • delete() − Deletes an element from the data structure

    Hash tables approach the deletion process in O(1) time while Binary Search Trees and Balanced Search Trees like AVL Trees or Red-Black Trees take O(logN) time for the deletion operation.

  • search() − Searches for a given element in the data structure and returns if it is present or not

    Hash tables and HashMap with Separate Chaining take O(1) time for searching while Binary Search Trees and Balanced Search Trees like AVL Trees or Red-Black Trees take O(logN) time for the searching operation.

  • getRandom() − Returns a random element stored in the data structure

    Dynamic Array with random shuffling and HashMap offer O(1) complexity to get a random element from the data structure while Balanced Search Trees like AVL Trees or Red-Black Trees take o(logN) time for the same operation.

Problem Statement

Design an efficient data structure with minimal time and space complexity for the following operations −

  • insert()

  • remove()

  • search()

  • getRandom()

  • removeRandom()

  • size()

Sample Example

Input

  • insert(2)

  • insert(3)

  • insert(4)

  • search(6)

  • getRandom()

  • remove(2)

  • size()

  • removeRandom()

Output

  • -

  • -

  • -

  • False

  • 3

  • -

  • 2

  • 4

Explanation

Data structure on initialization: A ={}

  • On insertion of 2: A={2}

  • On insertion of 3: A={2,3}

  • On insertion of 4: A={2,3,4}

  • On searching 6 in A: False

  • Getting a random element from A: 3

  • Removing 2 from A: A={3,4}

  • The size of the data structure is 2

  • Deleting a random element from A: A={3} (4 is deleted and returned)

Solution Approach

Use the following for defining our data structure −

  • Unordered Map − It stores the elements of the data structure as keys and their indexes in a dynamic array. It is used to allow efficient access and deletion of elements.

  • Dynamic Array − It stored the elements of the data structure in the order they were inserted. It allows the efficient working of random functions.

  • Random number generator

Algorithm

  • Initialize an empty set (element) to store the values in the order elements are added.

  • Initialize a hasp map with keys as an element and values as the index of the elements stored in the set.

  • Initialize a random number generator.

  • Implement the following methods −

    • insert(ele) − add an element to the set if it is not present and add the index along with the element to the hashmap.

    • search(ele) − searches for the element by searching the hashmap.

    • getRandom() − the random number generates a random index and the element at that index is returned.

    • removeRandom() − the random number generator generates a random index and the element at that index is deleted from both the set and hashmap.

    • remove() − removing a specific element by getting its index from the hashmap and deleting the element from both set and hashmap.

    • size() − returns the size of the set.

Example

#include <iostream>
#include <vector>
#include <unordered_map>
#include <stdexcept>
#include <random>
#include <ctime>
#include <algorithm>

template <typename T>
class RandomizedSet {
private:
   std::unordered_map<T, int> elementIndexes;
   std::vector<T> element;
   std::mt19937 rand;

public:
   RandomizedSet() {
      elementIndexes.clear();
      element.clear();
      rand = std::mt19937(std::time(0));
   }

   void insert(T ele) {
      if (!search(ele)) {
         int lastIndex = element.size();
         elementIndexes[ele] = lastIndex;
         element.push_back(ele);
      }
   }
   bool search(T ele) {
      return elementIndexes.find(ele) != elementIndexes.end();
   }
   T getRandom() {
      if (elementIndexes.empty()) {
         throw std::out_of_range("Empty set, cannot get a random element.");
      }
      std::uniform_int_distribution<int> dist(0, element.size() - 1);
      int randomIndex = dist(rand);
      return element[randomIndex];
   }
   T removeRandom() {
      if (elementIndexes.empty()) {
         throw std::out_of_range("Empty set, cannot remove a random element.");
      }
      std::uniform_int_distribution<int> dist(0, element.size() - 1);
      int randomIndex = dist(rand);
      return removeElement(randomIndex); // Corrected function name
   }
   T remove(T ele) {
      if (!search(ele)) {
         throw std::out_of_range("Element not found in the set.");
      }
      int index = elementIndexes[ele];
      return removeElement(index); // Corrected function name
   }
   int size() {
      if (element.size() != elementIndexes.size()) {
         throw std::runtime_error("Inconsistent set size, internal error.");
      }
      return element.size();
   }

private:
   T removeElement(int currentIndex) { // Corrected function name
      T currentElement = element[currentIndex];
      int lastIndex = element.size() - 1;
      T lastVal = element[lastIndex];
      std::swap(element[currentIndex], element[lastIndex]);
      element.pop_back();
      elementIndexes[lastVal] = currentIndex;
      elementIndexes.erase(currentElement);
      return currentElement;
   }
};

#include <iostream>

int main() {
   // Create an instance of the RandomizedSet for integers
   RandomizedSet<int> mySet;

   // Insert elements
   mySet.insert(42);
   mySet.insert(99);

   // Check if an element exists
   bool exists = mySet.search(42);

   // Remove a specific element
   mySet.remove(42);

   // Get a random element
   int randomElement = mySet.getRandom();

   // Get the size of the set
   int setSize = mySet.size();

   // Output results
   std::cout << "Element 42 exists: " << exists << std::endl;
   std::cout << "Random element: " << randomElement << std::endl;
   std::cout << "Set size: " << setSize << std::endl;

   return 0;
}

Output

Element 42 exists: 1
Random element: 99
Set size: 1

Time Complexity −

  • insert() = O(1)

  • search() = O(1)

  • getRandom() = O(1)

  • removeRandom() = O(1)

  • remove() = O(1)

  • size() = O(1)

Space Complexity −

  • element[] array = O(n)

  • elementIndexes hashmap = O(n)

Conclusion

In conclusion, the RandomizedSet structure provides an efficient approach to perform functions with O(1) complexity. The operations included insert, search, getRandom, removeRandom, remove and size.

Updated on: 03-Nov-2023

98 Views

Kickstart Your Career

Get certified by completing the course

Get Started
Advertisements