Advanced Hashing Techniques Unveiled: Cuckoo Hashing [Series 1]
Hashing, a fundamental data structure, underpins countless software applications for its speed, efficiency, and versatility. Despite its perceived simplicity, aspiring programmers should explore the intricacies of hashing techniques. This knowledge is ubiquitous and essential for safeguarding sensitive data, fine-tuning search algorithms, or upholding data integrity, hashing plays a pivotal role in software development.
While many hashing techniques are available, I set my sights on unraveling three advanced techniques through captivating articles. There, I'll untangle the core concepts, drape them with sample code snippets, and explain some real-world applications. Brace yourself for the unveiling of Cuckoo Hashing — the opening act of our hashing saga. And if curiosity beckons, stay tuned for the forthcoming chapters.
If you find it insightful and appreciate my writing, consider following me for updates on future content. I'm committed to sharing my knowledge and contributing to the coding community. Join me in spreading the word and helping others to learn.
Introduction
Cuckoo Hashing is a hash table collision resolution technique that ensures constant-time lookup by using two hash functions. It employs multiple-choice hash tables and guarantees that each key is stored in one of the tables.
Now you might be wondering what is multiple-choice hash tables. Consider it like finding the right box for your toy. If one box is full, you try another until you find an empty one. It's the same with storing data in different boxes until you find a place without any stuff in it. That's multiple-choice hashing!
When a collision occurs, the existing item is relocated to its alternate position in the other table. This process continues recursively until all keys find a place or a predefined threshold is reached, indicating a table expansion.
Cuckoo Hashing offers efficient retrieval and insertion operations with O(1) time complexity, with worst-case constant-time complexity, making it suitable for applications requiring fast key-value lookups while minimizing memory overhead.
How Does it work
I'll try articulating of how Cuckoo Hashing works for adding (inserting) and searching for an item.
Adding (Inserting) an Item:
Searching for an Item:
I found this funny flow chart that lucidly explains this technique. If you want to know more, read through this document: https://www.brics.dk/RS/01/32/BRICS-RS-01-32.pdf
领英推荐
Pros and Cons
There are several advantages and disadvantages of this approach. Some primary advantages are as follows:
There are some challenges with this technique as well:
Real-time Use Cases
Cuckoo Hashing is utilized in various real-time use cases across several domains due to its efficient lookup and insertion operations. Some common real-time use cases of Cuckoo Hashing include:
Code Snippet
Enough of theories; let's dive into some practical code snippets. I've written the following code with inline comments to ensure a clear understanding of its purpose.
class CuckooHashing {
/**
* Constructor for the CuckooHashing class.
*/
constructor(size) {
this.size = size; // The size of the hash tables
// Initialize two hash tables with null values
this.table1 = new Array(size).fill(null);
this.table2 = new Array(size).fill(null);
}
/**
* Inserts a key-value pair into the hash tables.
*/
insert(key, value) {
const hash1 = this.hashFunction1(key);
const hash2 = this.hashFunction2(key);
if (this.table1[hash1] === null) {
// Check if the slot in table1 is empty, if so, insert the item
this.table1[hash1] = { key, value };
} else if (this.table2[hash2] === null) {
// If slot in table1 is occupied, check table2 and insert if slot is empty
this.table2[hash2] = { key, value };
} else {
// If both slots are occupied, perform eviction and recursive insertion
const { key: evictedKey, value: evictedValue } = this.table1[hash1];
this.table1[hash1] = { key, value };
this.insert(evictedKey, evictedValue); // Recursive call to handle evicted item
}
}
/**
* Searches for a key in the hash tables and returns the corresponding value if found.
*/
search(key) {
const hash1 = this.hashFunction1(key);
const hash2 = this.hashFunction2(key);
if (this.table1[hash1] !== null && this.table1[hash1].key === key) {
// Check if key is found in table1
return this.table1[hash1].value;
} else if (this.table2[hash2] !== null && this.table2[hash2].key === key) {
// Check if key is found in table2
return this.table2[hash2].value;
} else {
// Key not found
return null;
}
}
/**
* First hash function for calculating the index in table1.
*/
hashFunction1(key) {
return hash(key) % this.size;
}
/**
* Second hash function for calculating the index in table2.
*/
hashFunction2(key) {
return Math.floor(hash(key) / this.size) % this.size;
}
}
This snippet is well-documented. However, if you need further clarification or assistance in understanding any part of it, please feel free to leave a comment. I'll be happy to provide additional explanations as needed.