For a pattern and a text , the simplest substring searching algorithm performs a symbol-by-symbol comparison of the pattern with each substring of the text, which results in
running time. If you work with large texts and performance is crucial, this might be too slow. To find a substring faster, we need to use more efficient approaches than the naive direct comparison.
One of the algorithms that help search a substring faster is the Rabin-Karp algorithm. It uses string hashing for a faster comparison thus significantly reducing the total running time compared with the naive approach.
Algorithm description
The main idea of the Rabin-Karp algorithm is to use string hashing and compare strings' hash values instead of a direct comparison. To compute the substrings' hash values fast, the algorithm uses the polynomial hash functions and their rolling hash property. In more detail, the algorithm can be formulated as follows:
- Calculate the hash value for a pattern.
- Moving along the text (from right to left), calculate the hash value for the current substring of the text using the rolling hash property.
- If hash values for the pattern and the current substring are not equal, move to the next substring. Otherwise, perform a symbol-by-symbol comparison of the strings. If the strings are indeed equal, add the current index to a list of occurrences.
- Repeat steps 2-3 until all substrings of the text are processed. Then, return a list of all occurrences.
An example
Consider how the algorithm works for a pattern and a text . For simplicity, we will use the following constants for the polynomial hash function: and . First, we need to calculate the hash for the pattern:
The next step is to calculate the hash value for the first suffix of the text:
.
Since , we know that the strings are not equal as well. The next step is to calculate the hash value for the next substring of the text. Using the rolling hash property, you can do that as follows:
The hash values of the pattern and the current substring are equal, so we need to check that the strings are indeed equal. Since , the attempt is not successful, so we move to the next substring:
We can see that this hash value is not equal to the hash value of the pattern, thus the strings are not equal. The next substring to consider is :
The hash values as well as substring match, hence we have found an occurrence. The last substring to process is :
Since , the strings are not equal. All the substrings of the text are processed and the algorithm is finished.
Complexity analysis
Let's estimate the running time of the algorithm for a pattern and a text (assuming that ).
The first step of the algorithm is to calculate the hash value for the pattern and then for the first suffix of the text. This requires operations. After that, we calculate the hash value for the remaining substrings of the text using the rolling hash property. Each of these calculations can be done in . When the hash values of the pattern and the current substring are equal, we perform a symbol-by-symbol comparison which requires operations in the worst case. So, the overall running time of the algorithm can be estimated as
where is the number of times the pattern occurs in the text.
Note that in some cases, the algorithm may work in . For example, for and , the algorithm will perform a symbol-by-symbol comparison of the pattern with each substring of the text, which results in the worst-case running time.
Also, the same situation may happen if there are a lot of collisions between the pattern and substrings of the text. However, for the polynomial hash function, the probability of a collision is , which is low for a big .
To sum up, although in some cases the Rabin-Karp algorithm may work in , in reality, such cases are quite rare, which makes this algorithm a very good and efficient alternative to the naive substring searching approach.