Build a custom Map using Header file in C++

Last Updated : 5 Sep, 2026

A map is an associative container that stores data as key-value pairs, with each key uniquely associated with a value. It provides efficient access to values based on their corresponding keys.

  • The implementation is based on an AVL tree to maintain balanced operations.
  • It supports insertion, searching, deletion, updating, and traversal.

Example

A map stores each element as a key-value pair:

Key Value
10 50
20 80
30 90

Here, 10, 20, and 30 are keys, while 50, 80, and 90 are their corresponding values.

Approach

The custom map is implemented using an AVL tree, which keeps the tree balanced to provide efficient operations.

  • Create a Map node containing a key, value, child pointers, parent pointer, and height.
  • Store the nodes according to their keys, similar to a Binary Search Tree.
  • Balance the tree using AVL rotations after insertion and deletion.
  • Store the complete Map implementation in a header file.
  • Include the header file wherever the custom map is required.

Structure of the Custom Map

Each node of the AVL tree stores the key-value pair and the information required to maintain the tree.

  • first stores the key.
  • second stores the value associated with the key.
  • left and right point to the child nodes.
  • par points to the parent node.
  • depth stores the height of the subtree.
  • root stores the root of the AVL tree.
  • cnt stores the number of elements in the map.

The basic structure is:

class Map {
static Map* root;
static int cnt;

Map *left, *right, *par;

int first, second, depth;
};

Operations Supported by the Custom Map

The custom Map class implements the following operations:

1. insert()

This method is used to insert a key-value pair in the map. There are two insert() methods used here. 

Syntax:

map.insert(first, second);

One is made public and it takes two parameters:

  • first - It is the key
  • second - It is the respective value of the key
C++
void insert(int first, int second)
{
    Map* temp = iterator(first);

    // If element doesnot exist already
    if (temp == nullptr)
        insert(first)->second = second;
    
    // If element exists already update it
    else
        temp->second = second;
}

The other one is made private. This method is called inside the operator overloading function of []. 

  • It takes only one parameter: first (It is the key).
  • Creates the new node with "first" as the key and returns the instance of the node.

Syntax:

map[first] = second;

C++
Map* insert(int first)
{
    // Increase the number of elements
    cnt++;
    Map* newnode = create(first);

    // If empty tree simply create the root
    if (root == nullptr) {
        root = newnode;
        return root;
    }
    Map *temp = root, *prev = nullptr;
    while (temp != nullptr) {
        prev = temp;
        if (first < temp->first)
            temp = temp->left;
        else if (first > temp->first)
            temp = temp->right;
        else {
            free(newnode);

            // If element already exists
            // decrease the count
            cnt--;

            // If the key is found then it is
            // returned by reference so that it is
            // updatable
            return temp;
        }
    }
    if (first < prev->first)
        prev->left = newnode;
    else
        prev->right = newnode;
    
    newnode->par = prev;

    // Once inserted Check and balance the tree
    // at every node in path from "newnode" to "root"
    balance(newnode);

    // New object is inserted and returned to
    // initialize in the main during assignment
    return newnode;
}

int& operator[](int key) {
    return insert(key)->second;
}

2. find()

It is used to find an element. This is a public method. It takes one parameter: first (which is the key) and returns the reference associated with the key. Internally it calls the private method iterator() to find the key

Syntax:

map.find(first);

C++
Map* iterator(int first)
{
    Map* temp = root;
    while (temp != nullptr && temp->first != first) {
        if (first < temp->first) {
            temp = temp->left;
        }
        else {
            temp = temp->right;
        }
    }
    return temp;
}

Map* find(int first) { 
    return iterator(first); 
}

Time Complexity: O(logN) where N is the size of the map.

3. update()

This value is used to update the value associated with a key. This is a public method. It takes two parameters:

  • first: It is the key to be found.
  • second: It is the new value of the key.

The function calls the iterator() function to get the instance of the key and updates the value associated to that key. If no such key exists then no operation is performed.

Syntax:

map.update(first, second);

C++
void update(int first, int second)
{
    Map* temp = iterator(first);
    if (temp != nullptr) {
        temp->second = second;
    }
}

4. Accessing any key  

Any value can be accessed using the subscript operator[]. The concept of method overloading is used to implement the functionality. It is a public method and the search() function is called inside the overloading function. It takes one parameter: first (it is the value of the key)

Syntax:

map[first];

C++
const Map* iterator(int first) const
{
    Map* temp = root;
    while (temp != nullptr 
           && temp->first != first) {
        if (first < temp->first) 
            temp = temp->left;
        else 
            temp = temp->right;
    }
    return temp;
}

const int search(int first) const
{
    const Map* temp = iterator(first);

    // If element exists with the given key
    // return its value
    if (temp != nullptr)
        return temp->second;

    // If element doesn't exist
    // return default value of 0
    return 0;
}

const int operator[](int key) const
{
    // Search method is also qualified with const
    return search(key);
}

5. erase()

Deletes the node with the given key, replaces it with its in-order predecessor or successor, and calls balance() on the affected node’s parent to maintain the AVL tree balance.

Syntax:

map.erase(first);

C++
void erase(int first, Map* temp = root)
{
    Map* prev = 0;
    cnt--;
    while (temp != 0 && temp->first != first) {
        prev = temp;
        if (first < temp->first) {
            temp = temp->left;
        }
        else if (first > temp->first) {
            temp = temp->right;
        }
    }
    if (temp == nullptr) {
        cnt++;
        return;
    }
    if (cnt == 0 && temp == root) {
        free(temp);
        root = nullptr;
        return;
    }
    Map* l = inorderPredecessor(temp->left);
    Map* r = inorderSuccessor(temp->right);
    if (l == 0 && r == 0) {
        if (prev == 0) {
            root = 0;
        }
        else {
            if (prev->left == temp) {
                prev->left = 0;
            }
            else {
                prev->right = 0;
            }
            free(temp);
            balance(prev);
        }
        return;
    }

    Map* start;
    if (l != 0) {
        if (l == temp->left) {
            l->right = temp->right;
            if (l->right != 0) {
                l->right->par = l;
            }
            start = l;
        }
        else {
            if (l->left != 0) {
                l->left->par = l->par;
            }
            start = l->par;
            l->par->right = l->left;
            l->right = temp->right;
            l->par = 0;
            if (l->right != 0) {
                l->right->par = l;
            }
            l->left = temp->left;
            temp->left->par = l;
        }
        if (prev == 0) {
            root = l;
        }
        else {
            if (prev->left == temp) {
                prev->left = l;
                l->par = prev;
            }
            else {
                prev->right = l;
                l->par = prev;
            }
            free(temp);
        }
        balance(start);
        return;
    }
    else {
        if (r == temp->right) {
            r->left = temp->left;
            if (r->left != 0) {
                r->left->par = r;
            }
            start = r;
        }
        else {
            if (r->right != 0) {
                r->right->par = r->par;
            }
            start = r->par;
            r->par->left = r->right;
            r->left = temp->left;
            r->par = 0;
            if (r->left != 0) {
                r->left->par = r;
            }
            r->right = temp->right;
            temp->right->par = r;
        }
        if (prev == 0) {
            root = r;
        }
        else {
            if (prev->right == temp) {
                prev->right = r;
                r->par = prev;
            }
            else {
                prev->left = r;
                r->par = prev;
            }
            free(temp);
        }
        balance(start);
        return;
    }
}

6. count()

This method returns the count of a key in the map. This is a public method. It takes one parameter: first(which is the key of value whose count should be found). This method calls the iterator() method internally and if no node is found then count is 0. Otherwise, count returns 1.

Syntax:

map.count(first);

C++
int count(int first)
{
    Map* temp = iterator(first);

    // If key is found
    if (temp != nullptr)
        return 1;

    // If key is not found
    return 0;
}

7. size()

This method returns the size of the map. This is a public method. This method does not take any parameter.

Syntax:

map.size();

C++
int size(void) { 
    return cnt; 
}

8. empty()

This method checks if the map is empty or not. This is a public method. It returns true if the map is empty, else false. This method does not take any parameter. 

Syntax:

map.empty();

C++
bool empty(void)
{
    if (root == 0)
        return true;
    return false;
}

9. clear()

This method is used to delete the whole map in. This is a public method. It does not take any parameter. It takes the erase() method internally.

Syntax:

map.clear();

C++
void clear(void)
{
    while (root != nullptr) {
        erase(root->first);
    }
}

10. iterate()

This method is used to traverse the whole map. This is a public method. It also does not take any parameter. The nodes are printed in the sorted manner of key.

Syntax:

map.iterate();

C++
void iterate(Map* head = root)
{
    if (root == 0)
        return;
    if (head->left != 0) {
        iterate(head->left);
    }
  
    cout << head->first << ' ';
  
    if (head->right != 0) {
        iterate(head->right);
    }
}

Creating the Custom Map Header File

Create a header file named map.h and place the complete Map class implementation in it.

C++
#include <iostream>
using namespace std;

// Custom Map Class
class Map {
private:
    Map* iterator(int first)
    {
        // A temporary variable created
        // so that we do not
        // lose the "root" of the tree
        Map* temp = root;

        // Stop only when either the key is found
        // or we have gone further the leaf node
        while (temp != nullptr &&
            temp->first != first) {

            // Go to left if key is less than
            // the key of the traversed node
            if (first < temp->first) {
                temp = temp->left;
            }

            // Go to right otherwise
            else {
                temp = temp->right;
            }
        }
        // If there doesn't exist any element
        // with first as key, nullptr is returned
        return temp;
    }

    // Returns the pointer to element
    // whose key matches first.
    // Specially created for search method
    // (because search() is const qualified).
    const Map* iterator(int first) const
    {
        Map* temp = root;
        while (temp != nullptr
            && temp->first != first) {
            if (first < temp->first) {
                temp = temp->left;
            }
            else {
                temp = temp->right;
            }
        }
        return temp;
    }

    // The const property is used to keep the
    // method compatible with the method "const
    // int&[]operator(int) const"
    // Since we are not allowed to change
    // the class attributes in the method
    // "const int&[]operator(int) const"
    // we have to assure the compiler that
    // method called(i.e "search") inside it
    // doesn't change the attributes of class
    const int search(int first) const
    {
        const Map* temp = iterator(first);
        if (temp != nullptr) {
            return temp->second;
        }
        return 0;
    }

    // Utility function to return the Map* object
    // with its members initialized
    // to default values except the key
    Map* create(int first)
    {
        Map* newnode = (Map*)malloc(sizeof(Map));
        newnode->first = first;
        newnode->second = 0;
        newnode->left = nullptr;
        newnode->right = nullptr;
        newnode->par = nullptr;

        // Depth of a newnode shall be 1
        // and not zero to differentiate
        // between no child (which returns
        // nullptr) and having child(returns 1)
        newnode->depth = 1;
        return newnode;
    }

    // All the rotation operation are performed
    // about the node itself
    // Performs all the linking done when there is
    // clockwise rotation performed at node "x"
    void right_rotation(Map* x)
    {
        Map* y = x->left;
        x->left = y->right;
        if (y->right != nullptr) {
            y->right->par = x;
        }
        if (x->par != nullptr && x->par->right == x) {
            x->par->right = y;
        }
        else if (x->par != nullptr && x->par->left == x) {
            x->par->left = y;
        }
        y->par = x->par;
        y->right = x;
        x->par = y;
    }

    // Performs all the linking done when there is
    // anti-clockwise rotation performed at node "x"
    void left_rotation(Map* x)
    {
        Map* y = x->right;
        x->right = y->left;
        if (y->left != nullptr) {
            y->left->par = x;
        }
        if (x->par != nullptr && x->par->left == x) {
            x->par->left = y;
        }
        else if (x->par != nullptr && x->par->right == x) {
            x->par->right = y;
        }
        y->par = x->par;
        y->left = x;
        x->par = y;
    }

    // Draw the initial and final graph of each
    // case(take case where every node has two child)
    // and update the nodes depth before any rotation
    void helper(Map* node)
    {
        // If left skewed
        if (depthf(node->left)
            - depthf(node->right) > 1) {

            // If "depth" of left subtree of
            // left child of "node" is
            // greater than right
            // subtree of left child of "node"
            if (depthf(node->left->left)
                > depthf(node->left->right)) {
                node->depth
                    = max(depthf(node->right) + 1,
                        depthf(node->left->right) + 1);
                node->left->depth
                    = max(depthf(node->left->left) + 1,
                        depthf(node) + 1);
                right_rotation(node);
            }

            // If "depth" of right subtree
            // of left child of "node" is
            // greater than
            // left subtree of left child
            else {
                node->left->depth = max(
                    depthf(node->left->left) + 1,
                    depthf(node->left->right->left)
                    + 1);
                node->depth
                    = max(depthf(node->right) + 1,
                    depthf(node->left->right->right) + 1);
                node->left->right->depth
                    = max(depthf(node) + 1,
                        depthf(node->left) + 1);
                left_rotation(node->left);
                right_rotation(node);
            }
        }

        // If right skewed
        else if (depthf(node->left)
                - depthf(node->right) < -1) {

            // If "depth" of right subtree of right
            // child of "node" is greater than
            // left subtree of right child
            if (depthf(node->right->right)
                > depthf(node->right->left)) {
                node->depth
                    = max(depthf(node->left) + 1,
                        depthf(node->right->left) + 1);
                node->right->depth
                    = max(depthf(node->right->right) + 1,
                        depthf(node) + 1);
                left_rotation(node);
            }

            // If "depth" of left subtree
            // of right child of "node" is
            // greater than that of right
            // subtree of right child of "node"
            else {
                node->right->depth = max(
                    depthf(node->right->right) + 1,
                    depthf(node->right->left->right) + 1);
                node->depth = max(
                    depthf(node->left) + 1,
                    depthf(node->right->left->left) + 1);
                node->right->left->depth
                    = max(depthf(node) + 1,
                        depthf(node->right) + 1);
                right_rotation(node->right);
                left_rotation(node);
            }
        }
    }

    // Balancing the tree about the "node"
    void balance(Map* node)
    {
        while (node != root) {
            int d = node->depth;
            node = node->par;
            if (node->depth < d + 1) {
                node->depth = d + 1;
            }
            if (node == root
                && depthf(node->left) 
                - depthf(node->right) > 1) {
                if (depthf(node->left->left)
                    > depthf(node->left->right)) {
                    root = node->left;
                }
                else {
                    root = node->left->right;
                }
                helper(node);
                break;
            }
            else if (node == root
                    && depthf(node->left) 
                    - depthf(node->right)
                            < -1) {
                if (depthf(node->right->right)
                    > depthf(node->right->left)) {
                    root = node->right;
                }
                else {
                    root = node->right->left;
                }
                helper(node);
                break;
            }
            helper(node);
        }
    }

    // Utility method to return the
    // "depth" of the subtree at the "node"
    int depthf(Map* node)
    {
        if (node == nullptr)

            // If it is null node
            return 0;
        return node->depth;
    }

    // Function to insert a value in map
    Map* insert(int first)
    {
        cnt++;
        Map* newnode = create(first);
        if (root == nullptr) {
            root = newnode;
            return root;
        }
        Map *temp = root, *prev = nullptr;
        while (temp != nullptr) {
            prev = temp;
            if (first < temp->first) {
                temp = temp->left;
            }
            else if (first > temp->first) {
                temp = temp->right;
            }
            else {
                free(newnode);
                cnt--;
                return temp;
            }
        }
        if (first < prev->first) {
            prev->left = newnode;
        }
        else {
            prev->right = newnode;
        }
        newnode->par = prev;
        balance(newnode);
        return newnode;
    }

    // Returns the previous node in
    // inorder traversal of the AVL Tree.
    Map* inorderPredecessor(Map* head)
    {
        if (head == nullptr)
            return head;
        while (head->right != nullptr) {
            head = head->right;
        }
        return head;
    }

    // Returns the next node in
    // inorder traversal of the AVL Tree.
    Map* inorderSuccessor(Map* head)
    {
        if (head == nullptr)
            return head;
        while (head->left != nullptr) {
            head = head->left;
        }
        return head;
    }

public:
    // Root" is kept static because it's a class
    // property and not an instance property
    static class Map* root;
    static int cnt;

    // "first" is key and "second" is value
    Map *left, *right, *par;
    int first, second, depth;

    // overloaded [] operator for assignment or
    // inserting a key-value pairs in the map
    // since it might change the members of
    // the class therefore this is
    // invoked when any assignment is done
    int& operator[](int key) {
        return insert(key)->second;
    }

    // Since we have two methods with
    // the same name "[]operator(int)" and
    // methods/functions cannot be
    // distinguished by their return types
    // it is mandatory to include a const
    // qualifier at the end of any of the methods

    // This method will be called from a const
    // reference to the object of Map class

    // It will not be called for assignment
    // because it doesn't allow to change
    // member variables

    // We cannot make it return by reference
    // because the variable "temp" returned
    // by the "search" method is
    // statically allocated and therefore
    // it's been destroyed when it is called out
    const int operator[](int key) const
    {
        return search(key);
    }

    // Count returns whether an element
    // exists in the Map or not
    int count(int first)
    {
        Map* temp = iterator(first);
        if (temp != nullptr) {
            return 1;
        }
        return 0;
    }

    // Returns number of elements in the map
    int size(void) {
        return cnt;
    }

    // Removes an element given its key
    void erase(int first, Map* temp = root)
    {
        Map* prev = nullptr;
        cnt--;
        while (temp != nullptr &&
            temp->first != first) {
            prev = temp;
            if (first < temp->first) {
                temp = temp->left;
            }
            else if (first > temp->first) {
                temp = temp->right;
            }
        }
        if (temp == nullptr) {
            cnt++;
            return;
        }
        if (cnt == 0 && temp == root) {
            free(temp);
            root = nullptr;
            return;
        }
        Map* l
            = inorderPredecessor(temp->left);
        Map* r
            = inorderSuccessor(temp->right);
        if (l == nullptr && r == nullptr) {
            if (prev == nullptr) {
                root = nullptr;
            }
            else {
                if (prev->left == temp) {
                    prev->left = nullptr;
                }
                else {
                    prev->right = nullptr;
                }
                free(temp);
                balance(prev);
            }
            return;
        }
        Map* start;
        if (l != nullptr) {
            if (l == temp->left) {
                l->right = temp->right;
                if (l->right != nullptr) {
                    l->right->par = l;
                }
                start = l;
            }
            else {
                if (l->left != nullptr) {
                    l->left->par = l->par;
                }
                start = l->par;
                l->par->right = l->left;
                l->right = temp->right;
                l->par = nullptr;
                if (l->right != nullptr) {
                    l->right->par = l;
                }
                l->left = temp->left;
                temp->left->par = l;
            }
            if (prev == nullptr) {
                root = l;
            }
            else {
                if (prev->left == temp) {
                    prev->left = l;
                    l->par = prev;
                }
                else {
                    prev->right = l;
                    l->par = prev;
                }
                free(temp);
            }
            balance(start);
            return;
        }
        else {
            if (r == temp->right) {
                r->left = temp->left;
                if (r->left != nullptr) {
                    r->left->par = r;
                }
                start = r;
            }
            else {
                if (r->right != nullptr) {
                    r->right->par = r->par;
                }
                start = r->par;
                r->par->left = r->right;
                r->left = temp->left;
                r->par = nullptr;
                if (r->left != nullptr) {
                    r->left->par = r;
                }
                r->right = temp->right;
                temp->right->par = r;
            }
            if (prev == nullptr) {
                root = r;
            }
            else {
                if (prev->right == temp) {
                    prev->right = r;
                    r->par = prev;
                }
                else {
                    prev->left = r;
                    r->par = prev;
                }
                free(temp);
            }
            balance(start);
            return;
        }
    }
    
    // Returns if the map is empty or not
    bool empty(void)
    {
        if (root == nullptr)
            return true;
        return false;
    }
    
    // Given the key of an element it updates
    // the value of the key
    void update(int first, int second)
    {
        Map* temp = iterator(first);
        if (temp != nullptr) {
            temp->second = second;
        }
    }

    // Deleting the root of
    // the tree each time until the map
    // is not empty
    void clear(void)
    {
        while (root != nullptr) {
            erase(root->first);
        }
    }

    // Inorder traversal of the AVL tree
    void iterate(Map* head = root)
    {
        if (root == nullptr)
            return;
        if (head->left != nullptr) {
            iterate(head->left);
        }
        cout << head->first << ' ';
        if (head->right != nullptr) {
            iterate(head->right);
        }
    }

    // Returns a pointer/iterator to the element
    // whose key is first
    Map* find(int first) {
        return iterator(first);
    }

    // Overloaded insert method,
    // takes two parameters - key and value
    void insert(int first, int second)
    {
        Map* temp = iterator(first);
        if (temp == nullptr) {
            insert(first)->second = second;
        }
        else {
            temp->second = second;
        }
    }
};

Map* Map::root = nullptr;
int Map::cnt = 0;

Now save it as a header file say map.h to include it in other codes and implement the functionalities.

Executing the Custom Map

Follow these steps to use the custom map:

  • Create the header file as map.h.
  • Store map.h in the same directory as the C++ source file.
  • Include the header using #include "map.h".
  • Create a Map object and use its supported operations.
  • Compile and run the program.

Examples

The following examples demonstrate the operations supported by the custom Map class.

Example 1: Programs to demonstrate the use of insert(), accessing any key and update() methods

C++
#include "map.h"
#include <iostream>
using namespace std;

int main()
{
    Map map;

    // 1st way of insertion
    map[132] = 3;
    map[34] = 5;
    map[42] = -97;
    map[22] = 10;
    map[12] = 42;

    // 2nd way of insertion
    map.insert(-2,44);
    map.insert(0,90);

    // accessing elements
    cout<<"Value at key 42 before updating = "
        <<map[42]<<endl;
    cout<<"Value at key -2 before updating = "
        <<map[-2]<<endl;
    cout<<"Value at key 12 before updating = "
        <<map[12]<<endl;

    // Updating value at key 42
    map[42] = -32;

    // Updating value at key -2
    map.insert(-2,8);

    // Updating value at key 12
    map.update(12,444);

    // accessing elements
    cout<<"Value at key 42 after updating = "
        <<map[42]<<endl;
    cout<<"Value at key -2 after updating = "
        <<map[-2]<<endl;
    cout<<"Value at key 12 after updating = "
        <<map[12]<<endl;
    cout<<"Value at key 0 = "<<map[0]<<endl;    
    return 0;
}

Output

Example 2: Programme to demonstrate the use of erase(), clear() and iterate() methods

C++
#include "map.h"
#include <iostream>
using namespace std;

int main()
{
    Map map;
    map[132] = 3;
    map[34] = 5;
    map[42] = -97;
    map[22] = 10;
    map[12] = 42;

    // Iterating the Map elements before erasing 22
    map.iterate();
    map.erase(22);

    // Iterating the Map elements after erasing 22
    map.iterate();
    
    // Deleting the whole map
    map.clear();

    // Now since there are zero elements 
    // in the Map the output is blank
    cout<<"\nElements in Map after clear operation: ";
    map.iterate();
    return 0;
}

Output: 

Example 3: Programme to demonstrate the use of find(), count(), empty() and size() methods

C++
#include "map.h"
#include <iostream>
using namespace std;

int main()
{
    Map map;
    map[132] = 3;
    map[34] = 5;
    map[42] = -97;
    cout<<"Value at 132 before updating = "
        <<map[132]<<endl;

    // Find method returns pointer to element 
    // whose key matches given key
    Map *it = map.find(132);

    // Updating the value at key 132
    it->second = 98;

    cout<<"Value at 132 after updating = "
        <<map[132]<<endl;

    // Count of an element which is not present 
    // in the map is 0
    cout<<"Count of 77 = "<<map.count(77)<<endl;

    // Count of an element which is present 
    // in the map is 1
    cout<<"Count of 34 = "<<map.count(34)<<endl;

    // Size of map/number of elements in map
    cout<<"Map size = "<<map.size()<<endl;

    // Map is not empty therefore returned 0
    cout<<"Is map empty: "<<map.empty()<<endl;

    // Clearing the map
    map.clear();

    // Map is empty therefore return 1
    cout<<"Is map empty: "<<map.empty()<<endl;
    return 0;
}

Output

Comment