Data Structures

Hash Table Implementation using Linear Probing in C++

Linear Probing is the simplest approach to handle the collisions in Hash Table. For instance, if the hash index is already occupied, sequentially search for the free index and insert the new key-value pair.

Prefer the Linear Probing method when the application data is limited. Alternatively, look for the Hash Table implementation using the Separate Chaining method when the data is dynamic and expected to grow larger.

1. Hash Table Representation

An example Hash Table of size 8 is represented here. The hash() function returns the same index for the keys “delete” and “int”. The key “delete” is inserted on the hash index 3. Later the key “int” results in the same hash index 3 which is already occupied. As a result, search for the next free index and insert the key at index 4.

Hash Table using Linear Probing Method
Hash Table using Linear Probing Method
/**
 * Hash Table Node
 */
struct Node
{
	std::string key;
	int val;
};

// Hash Table
Node** hash_table = new Node*[capacity];

// Hash Table maximum capacity
int capacity;

2. Implementation of Hash Table using Linear Probing in C++

/**
 * C++ example to demonstrate Hash Table implementation using Linear Probing
 */

#include <iostream>
#include <iomanip>
using namespace std;

/**
 * Hash Table Node
 */
struct Node
{
	std::string key;
	int val;
};

/**
 * Hash Table implementation using Linear Probing
 */
class HashTable
{
private:
    // Hash Table
    Node** hash_table;
    
    // Hash Table maximum capacity
    int capacity;
    
    // Hash Table current size
    int size;
    
    // Empty node used for deletion
    Node* del_node;

public:
    // Constructor
    HashTable(int capacity);
    
    // Destructor
    ~HashTable();
    
    // Insert the key-value pair
    // Return the node inserted, NULL otherwise.
    Node* insert(std::string key, int val);
    
    // Delete the element by key
    // Returns true on success, false otherwise.
    bool remove(std::string key);
    
    // Access the key-value pair
    // Returns the node associated with the key
    Node* get(std::string key);
    
    // Traverse and print the hash table
    void display(std::string msg);
    
private:
    // Hash function to determine the index for every key
    int hash(std::string key, int capacity);
};

HashTable::HashTable(int capacity) : capacity(capacity), size(0)
{
    // Create the hash table for the given capacity 
    hash_table = new Node*[capacity];
    
    // Initialize the table entries to NULL
    for (int i=0; i < capacity; ++i) {
        hash_table[i] = NULL;
    }
    
    // Initialize the deleted node
    del_node = new Node();
    del_node->key = "";
    del_node->val = -1;
}

HashTable::~HashTable()
{
    for (int i=0; i < capacity; ++i) {
        if (hash_table[i] != NULL && ! hash_table[i]->key.empty()) {
            // Delete the table record
            delete hash_table[i];
            hash_table[i] = NULL;
        }
    }
    
    // Free the del node
    free(del_node);
}

Node* HashTable::insert(std::string key, int val)
{
    // Step 1. Check if the hash table size reached its capacity. If true, return.
    if (size == capacity) {
        return NULL;
    }
    
    // Step 2. Initialize the target index for insertion as -1.
    int target_index = -1;

    // Step 3. Determine the hash index for the key.
    int hash_index = hash(key, capacity);
    
    // Step 4. Check if the hash index is free. If true, set this as target index.
    if (hash_table[hash_index] == NULL ||
        hash_table[hash_index]->key.empty() || hash_table[hash_index]->key == key) {
            target_index = hash_index;
        
    } else {
        // Step 5. Otherwise, sequentially search the table for the free index.
        // Iterate the table once in circular motion from the next index.
        int i = (hash_index+1) % capacity;
        while (i != hash_index) {
            if (hash_table[i] == NULL || hash_table[i]->key.empty() || hash_table[i]->key == key) {
                // Step 6. If a free index is found, set this as target index and
                // stop the iteration
                target_index = i;
                break;
            }
            i = (i+1) % capacity;
        }
    }
    
    // Step 7. Create the new node.
    Node *new_node = new Node();
    new_node->key = key;
    new_node->val = val;
    
    // Step 8. Insert the new node to target index.
    hash_table[target_index] = new_node;
    ++size;
    
    return new_node;
}

bool HashTable::remove(std::string key)
{
    // Step 1. Initialize the target index as -1.
    int target_index = -1;
    
    // Step 2. Determine the hash index for the key.
    int hash_index = hash(key, capacity);
    
    // Step 3. Check if the key exists in the hash index.
    if (hash_table[hash_index]->key == key) {
        // Step 4. If true, set the hash index as target for deletion
        target_index = hash_index;
    } else {
    
        // Step 5. Otherwise, sequentially search the table for the key.
        // Iterate the table once in circular motion from the next index.
        // Stop the iteration if any NULL node found inbetween.
        int i = (hash_index+1) % capacity;
        while (i != hash_index && hash_table[i] != NULL) {
            if (hash_table[i]->key == key) {
                // Step 6. If key is found, set the index as target for deletion.
                target_index = i;
                break;
            }
            i = (i+1) % capacity;
        }
    }
    
    // Step 7. Otherwise, if the key is not found, return false.
    if (target_index == -1) {
        return false;
    }
    
    // Step 8. Delete the target node and return true.
    delete hash_table[target_index];
    hash_table[target_index] = del_node;
    --size;
    return true;
}

Node* HashTable::get(std::string key)
{
    // Step 1. Determine the hash index for the key
    int hash_index = hash(key, capacity);
    
    // Step 2. Check if the key is found on the hash index
    if (hash_table[hash_index]->key == key) {
        // Step 3. If true, return the node.
        return hash_table[hash_index];
    }
    
    // Step 3. Otherwise traverse the table once in circular motion from the next index.
    // Stop the iteration if any NULL node found inbetween.
    int i = (hash_index+1) % capacity;
    while (i != hash_index && hash_table[i] != NULL) {
        if (hash_table[i]->key == key) {
            // Step 4. If key is found, return the node.
            return hash_table[i];
        }
        i = (i+1) % capacity;
    }
    
    return NULL;
}

void HashTable::display(std::string msg)
{
    cout << msg << endl;
    cout << "(size = " << size << ")" << endl;
	
    // Traverse the entire hash table
    for (int i=0; i < capacity; ++i) {
        cout <<      "  +--------+--------+" << endl;
        cout << i << " |";
        Node* p = hash_table[i];
    	if (p == NULL ) {
    	    // NULL record, print empty
    	    cout << " " << setw(6) << "" << " | " << setw(6) << "" << " |";
  
        } else {
    	    // Print the record from the table
    	    cout << " " << setw(6) << left << p->key << " | " << setw(6) << right << p->val << " |";
    	}
    	cout << endl;
    }
    cout << "  +--------+--------+" << endl << endl;
}

int HashTable::hash(std::string key, int capacity)
{
    // A simple hash method to sum the ASCII values
    int hash = 0;
    for (int i=0; i < key.size(); ++i) {
        hash += key[i];
    }
    return hash % capacity;
}
 
// The main function to begin the execution   
int main()
{
    // Create the hash table of capacity 8
    HashTable keywords(8);
    
    // Insert key-value pairs
    keywords.insert("new", 1001);
    keywords.insert("delete", 1002);
    keywords.insert("int", 1003);
    keywords.insert("float", 1004);
    keywords.insert("if", 1005);
    keywords.insert("for", 1006);
    keywords.display("HASH TABLE after insertion of keywords 'new', 'delete', 'int', 'float', 'if', and 'for'");
    
    // Delete key-value pairs
    keywords.remove("int");
    keywords.remove("for");
    keywords.remove("delete");
    keywords.display("HASH TABLE after deletion of keywords 'int', 'for', and 'delete'");
    
    // Access a key
    Node* node = keywords.get("new");
    cout << "Accessing key 'new' returned value: " << node->val << endl;
    
    return 0;
}
Output
HASH TABLE after insertion of keywords 'new', 'delete', 'int', 'float', 'if', and 'for'
(size = 6)
  +--------+--------+
0 | for    |   1006 |
  +--------+--------+
1 |        |        |
  +--------+--------+
2 | new    |   1001 |
  +--------+--------+
3 | delete |   1002 |
  +--------+--------+
4 | int    |   1003 |
  +--------+--------+
5 |        |        |
  +--------+--------+
6 | float  |   1004 |
  +--------+--------+
7 | if     |   1005 |
  +--------+--------+

HASH TABLE after deletion of keywords 'int', 'for', and 'delete'
(size = 3)
  +--------+--------+
0 |        |     -1 |
  +--------+--------+
1 |        |        |
  +--------+--------+
2 | new    |   1001 |
  +--------+--------+
3 |        |     -1 |
  +--------+--------+
4 |        |     -1 |
  +--------+--------+
5 |        |        |
  +--------+--------+
6 | float  |   1004 |
  +--------+--------+
7 | if     |   1005 |
  +--------+--------+

Accessing key 'new' returned value: 1001

3. Hash Table Operations

3.1. Insert Key-Value Pair into Hash Table

Firstly, determine the array index by the key and proceed with the insertion. If the index is already occupied, sequentially search for the next free index in a circular motion. It means to proceed with the search from the top of the table when it reaches the end and continue until it reaches the hash index.

Algorithm

  1. Check if the hash table size reached its capacity. If true, return.
  2. Initialize the target index for insertion as -1.
  3. Determine the hash index for the key.
  4. Check if the hash index is free. If true, set this as the target index.
  5. Otherwise, sequentially search the table for the free index. Iterate the table once in a circular motion from the next index.
  6. If a free index is found, set this as the target index and stop the iteration.
  7. Create the new node.
  8. Insert the new node into the target index.

Examples

An example to demonstrate the hash table insertions using the linear probing method.

Hash Table insertions using the Linear Probing method
Hash Table insertions using the Linear Probing method
  • Insert the key “new” to index (2) as per the hash function.
  • Next, Insert the key “delete” to index (3) by following the same procedure.
  • The hash function returns the duplicate index (3) for the key “int”. But, the index is already occupied and has the key “delete”. So, we need to look for the subsequent indexes. Since the next index (4) is free, we can insert the “int”.

3.2. Delete Key-Value Pair from Hash Table

Firstly, determine the array index by the key, and sequentially search for the key. Proceed the search from the top of the table when it reaches the end until the starting point. Stop the search when the key is found, or encountered a NULL node. If the key exists, delete the key-value pair and replace it with an empty node.

Algorithm

  1. Initialize the target index as -1.
  2. Determine the hash index for the key.
  3. Check if the key exists in the hash index.
  4. If true, set the hash index as a target for deletion.
  5. Otherwise, sequentially search the table for the key. Iterate the table once in a circular motion from the next index. Stop the iteration if any NULL node is found in between.
  6. If the key is found, set the index as a target for deletion. Return false otherwise.
  7. Delete the target, replace it with an empty node, and return true.

Examples

An example to demonstrate the hash table deletion with the linear probing method.

Hash Table deletion using the Linear Probing method
Hash Table deletion using the Linear Probing method
  • The key “delete” is available at index (3). Remove the key-value pair by replacing it with -1.
  • Next, the key “new” is available at the index (2). Repeat the same procedure to remove the pair.
  • The next key “int” is not available at the index (3), so we need to probe the subsequent indexes. Finally, remove the key available at the next index (4).

3.3. Access the Key-Value Pair in Hash Table

Firstly, determine the array index by the key. Then check if the key is present on the index. Otherwise, search for the key in the subsequent indexes in a linear fashion.

Algorithm

  1. Determine the hash index for the key
  2. Check if the key is exits on the hash index
  3. Otherwise, traverse the table once in a circular motion from the next index. Stop the iteration if any NULL node is found in between.
  4. If the key is found, return the node, NULL otherwise.

Examples

Let’s see the examples to look up the keys “new” and “delete” from the following Hash Table.

Accessing data from Hash Table using the Linear Probing method
Accessing data from Hash Table using the Linear Probing method
  • Accessing the key “new” locates the exact index (2) using the hash function.
  • Whereas, accessing the key “int” is not straightforward. The hash function returns the index (3), but it is available at the index (4). We need to locate this by probing the sequent indexes.

4. References

Copyright copyright 2024 Simplerize