Rabin–Karp
Treat each window of the text as a number mod q, update it in O(1) with a rolling hash, and compare characters only when the remainders agree.
If the characters are digits, each window of m characters of the text is a number. Instead of comparing strings,
compare the numbers mod q:
- Time:
O(n + m) expected, O((n − m + 1)·m) worst - Space:
O(1) - CLRS, section 32.2
The text 2359023141526739921, the pattern 31415 and q = 13; one real match at shift 6 and one spurious hit at 12 (67399 is also 7 mod 13).
def rabin_karp(T, P, q):# T and P are strings of digits; each window of m digits is a number, compared mod q first.n, m = len(T), len(P)d = 10h = pow(d, m - 1, q) # the weight of the leading digit, mod qp = t = 0for i in range(min(m, n)):p = (d * p + int(P[i])) % qt = (d * t + int(T[i])) % qmatches = []spurious = []for s in range(n - m + 1):if p == t:if T[s:s + m] == P:matches.append(s)else:spurious.append(s)if s < n - m:t = (d * (t - int(T[s]) * h) + int(T[s + m])) % qreturn matches
Start
Local variables
Orange = changed in this step
Call stack
- rabin_karp()line 1
Explanation
The idea
If the characters are digits, each window of m characters of the text is a number. Instead of comparing strings,
compare the numbers mod q:
- the next window's remainder comes from the last one in
O(1)(a rolling hash): subtract the leading digit with weight10^(m−1), multiply by 10 and add the new digit; - if the remainders differ, there is surely no match;
- if they agree, there may be one; compare the characters. If not, it is a spurious hit.
Cost
With a large random q, spurious hits are rare: O(n + m) expected. In the worst case (say, a uniform text)
O((n − m + 1)·m). The idea works for other alphabets too (with the alphabet size as the radix).
What to watch in the trace
- The
tcard is the current window's remainder; compare the "bigger q" and "tiny q" inputs.