Definition · Naive string matchingGiven a text t of length n and a pattern p of length m with m ≤ n, the naive matcher considers each offset i from 0 to n − m in turn. At offset i it compares t[i+k] with p[k] for k = 0, 1, 2, ..., stopping at the first k where they differ, or reporting a match at i when all m agree. The offsets it considers are called its alignments, and there are n − m + 1 of them.
Nothing is remembered between alignments. That is the whole of the algorithm and also the whole of the criticism of it: after an alignment has matched five characters and failed on the sixth, the matcher throws those five away and starts the next offset from nothing. The rest of this module is three different things to do with the information that was thrown away.
Theorem · The comparison count is at most m(n − m + 1)On any text of length n and any pattern of length m, the naive matcher makes at most m(n − m + 1) character comparisons, and at least n − m + 1.
There are exactly n − m + 1 alignments, because i runs over the integers from 0 to n − m inclusive and the loop body is entered once for each.
At one alignment the inner loop increments k from 0 and stops the first time t[i+k] differs from p[k], or when k reaches m. Each iteration performs one comparison and k never exceeds m, so one alignment costs at most m comparisons; and the loop body runs at least once, so it costs at least 1. Multiplying both ends by the number of alignments gives the two bounds.
Both are attained. The lower one is reached whenever no character of the text equals p[0]; the upper one needs every alignment to match m − 1 characters and then fail, which is the input the sibling lesson “The Text Built to Reach the Bound” constructs.
□
Example · Forty-one alignments, broken down exactlyThe lab opens on the text the rain in spain stays mainly in the plain and the pattern ain, so n = 43, m = 3 and there are 41 alignments. Five of those offsets carry an a: 5, 14, 20, 25 and 40. The other 36 disagree on the very first comparison and cost 1 each. Offset 20 matches the a of stays and then fails on y, costing 2. The remaining four are matches and cost 3 each. So the total is 36 + 2 + 12 = 50, and the panel reports 50.
The bound over the same input is 3 × 41 = 123. The measurement is 50, which is 123/50 = 2.46 times smaller, and both numbers are right. Change one character of the text and 50 moves; 123 does not move until the lengths do.
Theorem · Expected comparisons per alignment over a uniform alphabetIf each character of the text is drawn independently and uniformly from an alphabet of sigma ≥ 2 letters, the expected number of comparisons the naive matcher makes at one alignment is at most sigma/(sigma − 1).
The alignment performs a comparison for k = 0, and performs the comparison at k only if the first k agreed, which for independent uniform characters has probability (1/sigma) to the power k.
So the expected count is the sum over k ≥ 0 of (1/sigma)^k, a geometric series with ratio 1/sigma < 1, which is exactly 1/(1 − 1/sigma) = sigma/(sigma − 1). Truncating the series at m terms, as the real algorithm does, can only reduce it.
The quantity m does not appear. A longer pattern gives more ways to fail early, not more work per alignment, and that is the whole reason the naive matcher is linear in practice.
□
At sigma = 13, the thirteen distinct characters of the opening input, the expectation is 13/12, just over 1.08. The measured figure is 50/41, just over 1.22. The two disagree because the text is English rather than uniform — the letter a is commoner than a thirteenth of the text — and the panel prints both rather than picking one.
Repeating the text does not produce a quadratic, and the lab says so
The first table under the chart repeats your text 1 to 6 times with the pattern unchanged. On the opening input the counts come out 50, 102, 154, 206, 258 and 310 as n runs 43, 86, 129, 172, 215, 258. The differences are 52 every time: that is a straight line, not a parabola.
It has to be. Holding m fixed makes the bound m(n − m + 1) linear in n as well, so nothing in that table can be quadratic in anything. The column worth reading is comparisons per character, which moves only from 1.16 to 1.20 across the six rows. The quadratic needs m to grow with n, which the second table does and this one deliberately does not.
The other two matchers on the same text
The panel also runs Knuth–Morris–Pratt and Horspool over whatever you typed, so that the comparison is on one input rather than between two anecdotes. On the opening text they take 44 and 25 against naive's 50. Horspool is ahead because it skips; KMP is behind by 6 because it reads every character of the text once and naive mostly reads one character per alignment and leaves.
All three report the same 4 matches, at offsets 5, 14, 25 and 40, and the panel checks all three against a scan that compares the whole substring at every offset. That check is not ceremony: a matcher that shifts too far returns a shorter list of positions, and a shorter list of correct positions is the one kind of wrong answer that looks exactly like a right one.