3.8.5 · D5String Algorithms

Question bank — Boyer-Moore — bad character, good suffix heuristics

1,743 words8 min readBack to topic

Quick symbol refresher so nothing is used unexplained:

Recall The symbols this page reuses
  • ::: the text being searched, length .
  • ::: the pattern we hunt for, length , indexed .
  • ::: the current alignment (how far is shifted right under ).
  • ::: the pattern index where the right-to-left scan hit a mismatch.
  • ::: the bad character , the text char that broke the match.
  • ::: rightmost index in holding , or if never appears in .
  • good suffix ::: the block that already matched before the mismatch.
  • border ::: a proper substring that is both a prefix and a suffix of a string.

True or false — justify

Boyer-Moore always reads every character of the text at least once.
False. On large alphabets it is often sublinear: a single mismatch near the pattern's end can force a jump of , so whole runs of text are never inspected — that is the entire point.
The bad-character rule alone can make the algorithm loop forever if coded naively.
True. When the raw formula is , a zero or backward shift; without the clamp the alignment never advances.
Taking the maximum of the two heuristic shifts can skip over a genuine match.
False. Each rule is independently a safe lower bound — neither ever skips a match — so the larger of two safe shifts is still safe, just faster.
If a mismatch occurs and does not appear in at all, the whole pattern can be slid past position .
True. With the shift is , sliding to just past the offending text char, because no alignment overlapping can ever match.
Comparing left-to-right would give the same skips as right-to-left.
False. Right-to-left scanning is what produces a matched suffix; the good-suffix rule and the "rightmost bad char" logic both depend on knowing the suffix, not a prefix — reverse it and the skipping logic collapses.
The good-suffix rule is only needed because the bad-character rule is slow to compute.
False. Both are cheap. Good suffix exists because in adversarial cases (e.g. the bad char equals a late occurrence) bad-character shift degenerates to ; good suffix supplies the large legal jump there.
After a full match is reported, the algorithm should shift by .
False. It shifts by , the good-suffix jump for the whole pattern — the width of the largest border tells you the next earliest place could reoccur, which is usually more than .
For a large alphabet, worst-case Boyer-Moore search time is .
False. is the best case. Basic BM's worst case is (e.g. , ); the Galil Rule repairs this to guaranteed .
can legitimately return a value larger than .
True. The rightmost in may sit to the right of the mismatch index; that is exactly the "would move backward" case that the clamp handles.
The good-suffix rule guarantees a shift of at least the length of the matched suffix.
False. It guarantees a safe shift, but Case 1 may only slide enough to align an internal copy of the suffix, which can be a small move; the length of the suffix is not a lower bound on the shift.

Spot the error

"Shift is the bad-character shift."
The error is dropping the clamp. When this is ; the correct rule is so the pattern always advances.
"The good suffix rule just checks whether the matched suffix reappears somewhere earlier in ."
That is only Case 1. It forgets Case 2: when no full copy exists, you must align a prefix of that equals a suffix of the good suffix. Skipping Case 2 over-shifts and misses real matches.
"When we find an internal copy of the good suffix, any copy will do."
No — the character preceding the internal copy must differ from . If it were equal, the next comparison would reproduce the very same mismatch, wasting the shift.
"On a full match we can just move to since the whole pattern is consumed."
Wrong for overlapping matches. Jumping by can leap past an occurrence that overlaps the current one; you must shift by , which respects the pattern's self-overlap (its border). ::: table for bad character costs extra space too, but that is a memory note, not this error.
"Boyer-Moore scans the pattern left to right, like a human reads."
The defining feature is the opposite: the inner loop starts at and decreases . Left-to-right scanning removes the suffix knowledge every skip relies on.
"Preprocessing the good-suffix array needs the text ."
No. Both heuristics preprocess only the pattern (and the alphabet). is untouched until the search phase — this is why BM handles searching one over many texts efficiently.

Why questions

Why does Boyer-Moore scan the pattern from right to left?
So that a mismatch leaves behind a matched suffix, which unlocks the good-suffix jump and lets the bad text char be aligned with its rightmost copy in — both large-skip mechanisms need suffix information.
Why do we align the bad char with the rightmost copy in to its left, not the leftmost?
Aligning with the leftmost copy could shift so far that it leaps over an alignment where the rightmost copy would have matched — that would skip a possible match. The rightmost-to-the-left copy is the largest safe jump.
Why is taking correct rather than adding them?
Adding could over-shift and skip a match. Each shift is separately the largest guaranteed-safe move for its own reason; the true safe move is whichever safe reason permits more, i.e. the maximum.
Why does the bad-character rule sometimes give only a shift of ?
When the bad char equals a character occurring late in (so ), the formula clamps to . This is the exact scenario where the good-suffix rule must rescue the shift.
Why does a large alphabet make Boyer-Moore faster?
With many distinct symbols, a random text char is unlikely to appear in , so mismatches frequently hit the "not in " case and jump the full pattern width — skipping many characters unread.
Why can't we discard the clamp by "guaranteeing" ?
We cannot guarantee it. The mismatched text char may equal a pattern char sitting to the right of , making perfectly possible on ordinary inputs, so the clamp is mandatory, not defensive fluff.
Why is BM's worst case despite the clever skips?
When and share a single repeated symbol (e.g. all a), every alignment matches almost fully then mismatches, giving tiny shifts of and near-full re-scans each time — the skips vanish. The Galil Rule fixes this.

Edge cases

If (pattern is a single character), what does the good-suffix rule contribute?
Almost nothing — there is no non-empty matched suffix before the sole character, so shifts come entirely from the bad-character rule; BM degenerates to a simple "jump to next occurrence" scan.
If has all distinct characters, is the bad-character rule enough?
Nearly. Distinct characters make bad-character shifts consistently large and the degenerate clamp-to- case rare, so good suffix rarely dominates — but you still keep both for correctness in the few overlap cases.
What happens when the mismatch is at (the very first comparison)?
The matched suffix is empty, so the good-suffix rule offers no help (, its default); the shift comes purely from the bad-character rule on that first char.
What if the alphabet contains characters never present in ?
for each such char, so any mismatch on them triggers the maximal bad-character jump of — these "foreign" characters are precisely what makes BM sublinear.
If two occurrences of overlap in , will BM still find both?
Yes, provided the post-match shift uses (the whole-pattern border), which never leaps past an overlapping start; that is exactly why we don't blindly shift by .
What is the shift when the good suffix has no internal reoccurrence and no prefix of matches its suffix?
The good-suffix array falls back to its default , sliding the pattern completely past the matched region — safe because nothing in can realign with it.
Does Boyer-Moore behave correctly when (text shorter than pattern)?
The search loop condition is immediately false, so it reports no matches without a single comparison — the correct answer, since cannot fit.

Recall Where these traps overlap other algorithms
  • The border concept driving good suffix is the same object built by the Borders and Prefix Function and reused in Knuth-Morris-Pratt (KMP).
  • The array is essentially a reversed Z-Algorithm computation.
  • For multiple patterns at once, prefer Aho-Corasick; for hashing-based matching, Rabin-Karp.
  • The worst-case cure lives at Galil Rule.