String matching: naive, KMP and Rabin-Karp
Finding a pattern in a text: slide and compare naively, or reuse what was matched with KMP, or hash the windows with Rabin-Karp.
Searching for a word in a document is pattern matching: you have a text of length and a pattern of length , and you want every position where the pattern occurs. It is a problem you can state in one sentence and optimise for decades, and the optimisations generalise into the tools behind grep, DNA alignment and plagiarism detection.
Do not throw away what you just learned
The naive method compares, fails, and starts over one character later — even though it already matched several characters. KMP records how much of the pattern matched and jumps to a position that reuses that knowledge. The prefix function is a map of "if I failed here, where could I possibly be?".
Type a text and pattern below, then step through the KMP scan and compare the work against the naive method.
Scan with KMP and watch the prefix function prevent backtracking
prefix function (LPS)
0, 0, 1, 2, 0
a matches a
KMP comparisons
19
naive comparisons
27
KMP never moves the text pointer backward: on a mismatch it consults LPS to reuse the part of the pattern already matched. On repetitive text that turns quadratic scanning into linear, which is why the gap above widens as the strings grow. Rabin-Karp instead hashes each window and only verifies on a hash hit.
Naive matching
Line the pattern up at each position and compare characters until a mismatch, then shift by one:
def naive(text, pattern):
for i in range(len(text) - len(pattern) + 1):
for j in range(len(pattern)):
if text[i + j] != pattern[j]:
break
else:
yield iThe worst case is — for example, a text of all as and a pattern of
as ending in b, where every alignment scans almost the whole pattern before
failing.
KMP and the prefix function
KMP preprocesses the pattern into a longest prefix-suffix (LPS) array. Entry
lps[i] is the length of the longest proper prefix of pattern[0..i] that is
also a suffix of it. On a mismatch at pattern index j, instead of restarting
at the next text character, KMP sets j = lps[j-1] and keeps the text pointer
where it is.
Because the text pointer never moves backward, the scan is after an preprocessing step — overall. The LPS array is the same "reuse overlapping structure" idea that appears in data compression and string algorithms generally.
Rabin-Karp and rolling hashes
Rabin-Karp avoids character comparisons almost entirely. It hashes the pattern and hashes each text window of length , using a rolling hash so the next window's hash is computed in from the previous one by removing the leading character and adding the trailing one:
On a hash match it verifies the actual characters, because different windows can collide. Expected time is ; a pathological input can force many verifications, but randomised moduli make that negligible. Rabin-Karp shines when searching for many patterns at once — hash them all and check the text's window hashes against the set.
A hash match is not a match
Two different strings can share a hash. Rabin-Karp must verify character by character on every hash hit, or it will report false positives. This is why the algorithm is probabilistic in its speed but exact in its answers.
In Python, ordinary substring search (sub in text, str.find) is implemented
in C with an efficient two-way algorithm, so you rarely write KMP by hand. It
matters when you need all occurrences, approximate matching, or the many-pattern
case.
Illustrative vs real
The demo uses a short text so each comparison is visible and reports exact counts for this input, not a worst-case proof. Real texts are gigabytes, alphabets may be bytes rather than characters, and production libraries use SIMD and Boyer-Moore-style skipping. The core trade — naive rescan versus reuse or hash — is exactly what the panel computes.
Check yourself
Eduspheria wiki · Programming & Data Structures, Algorithms
0 / 5 answered
From the exam paper
Modeled on NITJ AI-507, End-Sem December 2024
0 / 4 answered
Where next: these primitives compose — heaps power Dijkstra, hashing powers Rabin-Karp, and every one of them rests on the asymptotic vocabulary from the start of this chapter.