Welcome to the fascinating world of Data Structures and Algorithms! Today, we'll delve into the Rabin-Karp algorithm, a string-matching technique that's incredibly useful in real-world applications like text search engines and plagiarism detection.
The Rabin-Karp algorithm is a string-searching algorithm that allows us to find patterns within a text (called the text or the haystack) in a time complexity of O(n), where n is the length of the text. It's a variation of the Knuth-Morris-Pratt (KMP) and Boyer-Moore algorithms, but with a twist that makes it more efficient for large patterns.
Before we dive into the algorithm, let's familiarize ourselves with some essential concepts:
Hash Function: A hash function maps a string to an integer. In our case, we'll be using a hash function H to represent a pattern P of length m as a single integer.
Hash Value: The output of a hash function for a specific string is called the hash value.
Modulo Operation: The modulo operation (denoted by %) calculates the remainder of a division operation. This operation is used in the Rabin-Karp algorithm to handle potential hash collisions.
Now that we've covered the basics, let's delve into the Rabin-Karp algorithm. The algorithm can be divided into three main steps:
Preprocessing: In this step, we generate the hash value of the pattern (P) and calculate the hash value of each potential substring of the text (T) that might match the pattern. We also calculate the power of the base (a) for the length of the pattern.
Searching: In this step, we scan the text from left to right and, at each position, compare the hash value of the current substring of the text with the hash value of the pattern. If the hash values match, we perform a character-by-character comparison to confirm the match.
Handling Hash Collisions: If the hash values of the current substring and the pattern match, but the characters do not, we need to handle the hash collision. We slide the window (i.e., move the starting point of the substring) to the right and recalculate the hash value of the new substring, discarding the character that was moved out of the window.
What is the main advantage of the Rabin-Karp algorithm over other string-matching algorithms in terms of time complexity?
Here's a simple Python implementation of the Rabin-Karp algorithm:
def power(base, exp):
result = 1
while exp > 0:
result *= base
exp -= 1
return result
def hash_value(string, p, a):
hash_pattern, hash_substring, base = p[2], string[p[0]:p[1]+1], p[3]
return (hash_pattern - hash_substring * power(base, len(string) - len(p[1]) - p[0]) + power(base, len(string))) % p[3]
def search_pattern(text, pattern, base, hash_pattern):
hash_substring = hash_value(text, (0, 0), (len(text), base, hash_pattern))
if hash_substring == hash_pattern:
if len(pattern) == len(text):
return 0
for i in range(1, len(text)):
if hash_substring == hash_value(text, (i, i - len(pattern) + 1), (len(text), base, hash_pattern)):
if text[i:i+len(pattern)] == pattern:
return i - len(pattern) + 1
return -1In this implementation, we define three helper functions:
power(base, exp): Calculates the power of the base a to the exponent exp.
hash_value(string, p, a): Generates the hash value of a substring of the string that starts at the position p[0] and ends at the position p[1] using the base a and the current hash value of the pattern.
search_pattern(text, pattern, base, hash_pattern): Searches for the pattern in the text using the Rabin-Karp algorithm.
The Rabin-Karp algorithm can be further optimized using the "rolling hash" technique, which reduces the time complexity of the preprocessing step from O(m) to O(1), where m is the length of the pattern.
Implement the Rabin-Karp algorithm using the rolling hash technique and try it out on various texts and patterns. Can you find the pattern "banana" in the text "I love to eat bananas, apples, and oranges"?
Happy coding, and we'll see you in the next lesson! š