Linear Probing in Hash Tables
In Open Addressing, all elements are stored in the hash table itself. So at any point, size of table must be greater than or equal to total number of keys (Note that we can increase table size by copying old data if needed).
- Insert(k) - Keep probing until an empty slot is found. Once an empty slot is found, insert k.
- Search(k) - Keep probing until slot’s key doesn’t become equal to k or an empty slot is reached.
- Delete(k) - Delete operation is interesting. If we simply delete a key, then search may fail. So slots of deleted keys are marked specially as “deleted”.
Here, to mark a node deleted we have used dummy node with key and value -1.
Insert can insert an item in a deleted slot, but search doesn’t stop at a deleted slot.
The entire process ensures that for any key, we get an integer position within the size of the Hash Table to insert the corresponding value.
So the process is simple, user gives a (key, value) pair set as input and based on the value generated by hash function an index is generated to where the value corresponding to the particular key is stored. So whenever we need to fetch a value corresponding to a key that is just O(1).
Implementation:
#include <iostream>
#include <vector>
using namespace std;
// class to represent each key-value pair
class hashNode {
public:
// key and value
int key;
int value;
// constructor
hashNode(int key, int value) {
this->key = key;
this->value = value;
}
};
// class for hash map implementation
class hashMap {
hashNode** arr;
int capacity;
int size;
hashNode* dummy;
public:
// constructor to initialize members
hashMap() {
capacity = 20;
size = 0;
arr = new hashNode*[capacity];
for (int i = 0; i < capacity; i++)
arr[i] = NULL;
dummy = new hashNode(-1, -1);
}
// hash function to calculate index
int hashCode(int key) {
return key % capacity;
}
// function to insert a key-value pair
void insertNode(int key, int value) {
hashNode* temp = new hashNode(key, value);
int hashIndex = hashCode(key);
// linear probing to find correct index
while (arr[hashIndex] != NULL &&
arr[hashIndex]->key != key &&
arr[hashIndex]->key != -1) {
hashIndex++;
hashIndex %= capacity;
}
// insert node and update size if it's a new entry
if (arr[hashIndex] == NULL ||
arr[hashIndex]->key == -1)
size++;
arr[hashIndex] = temp;
}
// function to delete a node by key
int deleteNode(int key) {
int hashIndex = hashCode(key);
// search for the node with the given key
while (arr[hashIndex] != NULL) {
if (arr[hashIndex]->key == key) {
hashNode* temp = arr[hashIndex];
arr[hashIndex] = dummy;
size--;
return temp->value;
}
hashIndex++;
hashIndex %= capacity;
}
// key not found
return -1;
}
// function to get value for a given key
int get(int key) {
int hashIndex = hashCode(key);
int counter = 0;
// search for the key using linear probing
while (arr[hashIndex] != NULL) {
if (counter++ > capacity)
return -1;
if (arr[hashIndex]->key == key)
return arr[hashIndex]->value;
hashIndex++;
hashIndex %= capacity;
}
// key not found
return -1;
}
// function to return number of elements in map
int sizeofMap() {
return size;
}
// function to check if map is empty
bool isEmpty() {
return size == 0;
}
// function to print all key-value pairs
void display() {
for (int i = 0; i < capacity; i++) {
if (arr[i] != NULL && arr[i]->key != -1)
cout << arr[i]->key << " "
<< arr[i]->value << endl;
}
}
};
int main() {
hashMap* h = new hashMap;
h->insertNode(1, 1);
h->insertNode(2, 2);
h->insertNode(2, 3);
h->display();
cout << h->sizeofMap() << endl;
cout << h->deleteNode(2) << endl;
cout << h->sizeofMap() << endl;
cout << boolalpha << h->isEmpty() << endl;
cout << h->get(2);
return 0;
}
import java.lang.*;
class hashNode {
int key;
int value;
// constructor
public hashNode(int key, int value) {
this.key = key;
this.value = value;
}
}
class hashMap {
hashNode[] arr;
int capacity;
int size;
hashNode dummy;
// constructor
public hashMap() {
capacity = 20;
size = 0;
arr = new hashNode[capacity];
dummy = new hashNode(-1, -1);
}
// hash function
int hashCode(int key) {
return key % capacity;
}
// insert key-value pair
void insertNode(int key, int value) {
hashNode temp = new hashNode(key, value);
int hashIndex = hashCode(key);
while (arr[hashIndex] != null &&
arr[hashIndex].key != key &&
arr[hashIndex].key != -1) {
hashIndex++;
hashIndex %= capacity;
}
if (arr[hashIndex] == null || arr[hashIndex].key == -1)
size++;
arr[hashIndex] = temp;
}
// delete by key
int deleteNode(int key) {
int hashIndex = hashCode(key);
while (arr[hashIndex] != null) {
if (arr[hashIndex].key == key) {
hashNode temp = arr[hashIndex];
arr[hashIndex] = dummy;
size--;
return temp.value;
}
hashIndex++;
hashIndex %= capacity;
}
return -1;
}
// get value by key
int get(int key) {
int hashIndex = hashCode(key);
int counter = 0;
while (arr[hashIndex] != null) {
if (counter++ > capacity)
return -1;
if (arr[hashIndex].key == key)
return arr[hashIndex].value;
hashIndex++;
hashIndex %= capacity;
}
return -1;
}
// size of map
int sizeofMap() {
return size;
}
// check if map is empty
boolean isEmpty() {
return size == 0;
}
// display key-value pairs
void display() {
for (int i = 0; i < capacity; i++) {
if (arr[i] != null && arr[i].key != -1) {
System.out.println(arr[i].key +
" " + arr[i].value);
}
}
}
public static void main(String[] args) {
hashMap h = new hashMap();
h.insertNode(1, 1);
h.insertNode(2, 2);
h.insertNode(2, 3);
h.display();
System.out.println(h.sizeofMap());
System.out.println(h.deleteNode(2));
System.out.println(h.sizeofMap());
System.out.println(h.isEmpty());
System.out.println(h.get(2));
}
}
class hashNode:
# constructor
def __init__(self, key, value):
self.key = key
self.value = value
class hashMap:
# constructor
def __init__(self):
self.capacity = 20
self.size = 0
self.arr = [None] * self.capacity
self.dummy = hashNode(-1, -1)
# hash function
def hashCode(self, key):
return key % self.capacity
# insert key-value pair
def insertNode(self, key, value):
temp = hashNode(key, value)
hashIndex = self.hashCode(key)
while self.arr[hashIndex] is not None and \
self.arr[hashIndex].key != key and \
self.arr[hashIndex].key != -1:
hashIndex = (hashIndex + 1) % self.capacity
if self.arr[hashIndex] is None or \
self.arr[hashIndex].key == -1:
self.size += 1
self.arr[hashIndex] = temp
# delete by key
def deleteNode(self, key):
hashIndex = self.hashCode(key)
while self.arr[hashIndex] is not None:
if self.arr[hashIndex].key == key:
temp = self.arr[hashIndex]
self.arr[hashIndex] = self.dummy
self.size -= 1
return temp.value
hashIndex = (hashIndex + 1) % self.capacity
return -1
# get value by key
def get(self, key):
hashIndex = self.hashCode(key)
counter = 0
while self.arr[hashIndex] is not None:
if counter > self.capacity:
return -1
if self.arr[hashIndex].key == key:
return self.arr[hashIndex].value
hashIndex = (hashIndex + 1) % self.capacity
counter += 1
return -1
# return map size
def sizeofMap(self):
return self.size
# check if map is empty
def isEmpty(self):
return self.size == 0
# display all key-value pairs
def display(self):
for node in self.arr:
if node is not None and node.key != -1:
print(f"{node.key} {node.value}")
if __name__ == "__main__":
h = hashMap()
h.insertNode(1, 1)
h.insertNode(2, 2)
h.insertNode(2, 3)
h.display()
print(h.sizeofMap())
print(h.deleteNode(2))
print(h.sizeofMap())
print(str(h.isEmpty()).lower())
print(h.get(2))
using System;
class hashNode {
public int key;
public int value;
// constructor
public hashNode(int key, int value) {
this.key = key;
this.value = value;
}
}
class hashMap {
hashNode[] arr;
int capacity;
int size;
hashNode dummy;
// constructor
public hashMap() {
capacity = 20;
size = 0;
arr = new hashNode[capacity];
dummy = new hashNode(-1, -1);
}
// hash function
int hashCode(int key) {
return key % capacity;
}
// insert key-value pair
public void insertNode(int key, int value) {
hashNode temp = new hashNode(key, value);
int hashIndex = hashCode(key);
while (arr[hashIndex] != null &&
arr[hashIndex].key != key &&
arr[hashIndex].key != -1) {
hashIndex++;
hashIndex %= capacity;
}
if (arr[hashIndex] == null || arr[hashIndex].key == -1)
size++;
arr[hashIndex] = temp;
}
// delete by key
public int deleteNode(int key) {
int hashIndex = hashCode(key);
while (arr[hashIndex] != null) {
if (arr[hashIndex].key == key) {
hashNode temp = arr[hashIndex];
arr[hashIndex] = dummy;
size--;
return temp.value;
}
hashIndex++;
hashIndex %= capacity;
}
return -1;
}
// get value by key
public int get(int key) {
int hashIndex = hashCode(key);
int counter = 0;
while (arr[hashIndex] != null) {
if (counter++ > capacity)
return -1;
if (arr[hashIndex].key == key)
return arr[hashIndex].value;
hashIndex++;
hashIndex %= capacity;
}
return -1;
}
// size of map
public int sizeofMap() {
return size;
}
// check if map is empty
public bool isEmpty() {
return size == 0;
}
// display key-value pairs
public void display() {
for (int i = 0; i < capacity; i++) {
if (arr[i] != null && arr[i].key != -1) {
Console.WriteLine(arr[i].key +
" " + arr[i].value);
}
}
}
static void Main(string[] args) {
hashMap h = new hashMap();
h.insertNode(1, 1);
h.insertNode(2, 2);
h.insertNode(2, 3);
h.display();
Console.WriteLine(h.sizeofMap());
Console.WriteLine(h.deleteNode(2));
Console.WriteLine(h.sizeofMap());
Console.WriteLine(h.isEmpty().
ToString().ToLower());
Console.WriteLine(h.get(2));
}
}
class hashNode {
constructor(key, value) {
this.key = key;
this.value = value;
}
}
class hashMap {
constructor() {
this.capacity = 20;
this.size = 0;
this.arr = new Array(this.capacity).fill(null);
this.dummy = new hashNode(-1, -1);
}
hashCode(key) {
return key % this.capacity;
}
insertNode(key, value) {
let temp = new hashNode(key, value);
let hashIndex = this.hashCode(key);
while (this.arr[hashIndex] !== null &&
this.arr[hashIndex].key !== key &&
this.arr[hashIndex].key !== -1) {
hashIndex = (hashIndex + 1) % this.capacity;
}
if (this.arr[hashIndex] === null || this.arr[hashIndex].key === -1)
this.size++;
this.arr[hashIndex] = temp;
}
deleteNode(key) {
let hashIndex = this.hashCode(key);
while (this.arr[hashIndex] !== null) {
if (this.arr[hashIndex].key === key) {
let temp = this.arr[hashIndex];
this.arr[hashIndex] = this.dummy;
this.size--;
return temp.value;
}
hashIndex = (hashIndex + 1) % this.capacity;
}
return -1;
}
get(key) {
let hashIndex = this.hashCode(key);
let counter = 0;
while (this.arr[hashIndex] !== null) {
if (counter++ > this.capacity)
return -1;
if (this.arr[hashIndex].key === key)
return this.arr[hashIndex].value;
hashIndex = (hashIndex + 1) % this.capacity;
}
return -1;
}
sizeofMap() {
return this.size;
}
isEmpty() {
return this.size === 0;
}
display() {
for (let i = 0; i < this.capacity; i++) {
let node = this.arr[i];
if (node !== null && node.key !== -1)
console.log(`${node.key} ${node.value}`);
}
}
}
// Driver Code
let h = new hashMap();
h.insertNode(1, 1);
h.insertNode(2, 2);
h.insertNode(2, 3);
h.display();
console.log(h.sizeofMap());
console.log(h.deleteNode(2));
console.log(h.sizeofMap());
console.log(h.isEmpty());
console.log(h.get(2));
Output
1 1 2 3 2 3 1 false -1
Complexity analysis for Insertion:
Time Complexity:
- Best Case: O(1)
- Worst Case: O(n). This happens when all elements have collided and we need to insert the last element by checking free space one by one.
- Average Case: O(1) for good hash function, O(n) for bad hash function
Auxiliary Space: O(1)
Complexity analysis for Deletion:
Time Complexity:
- Best Case: O(1)
- Worst Case: O(n)
- Average Case: O(1) for good hash function; O(n) for bad hash function
Auxiliary Space: O(1)
Complexity analysis for Searching:
Time Complexity:
- Best Case: O(1)
- Worst Case: O(n)
- Average Case: O(1) for good hash function; O(n) for bad hash function
Auxiliary Space: O(1) for search operation