Fast Pattern Matching in Strings
This paper is under a licence that does not allow it to be reproduced, so what is here is its front matter and a short abstract. Read it at the publisher's page.
高速文字列パターン照合*
DONALD E. KNUTH†, JAMES H. MORRIS, JR.‡ AND VAUGHAN R. PRATT¶
要約。ある文字列内に別の与えられた文字列が出現するすべての位置を、その文字列の長さの和に比例する実行時間で発見するアルゴリズムを提示する。比例定数は、このアルゴリズムを実用に供するのに十分小さく、またこの手続きは、より一般的ないくつかのパターン照合問題を扱うよう拡張することもできる。このアルゴリズムの理論的応用により、偶数回文の連結の集合、すなわち言語が線形時間で認識可能であることを示す。平均的にはさらに高速に実行される他のアルゴリズムについても考察する。
キーワード。パターン、文字列、テキスト編集、パターン照合、トライメモリ、探索、文字列の周期、回文、最適アルゴリズム、フィボナッチ文字列、正規表現
テキスト編集プログラムでは、与えられた「パターン」文字列の出現を探すために、しばしば文字列を検索する必要がある。我々は、パターンがテキストの連続した部分文字列として現れるすべての位置、あるいは左端の位置だけを発見したい。例えば、はパターンを含むが、を部分文字列とはみなさない。
一致するパターンを探索する明白な方法は、テキストの各開始位置で探索を試み、誤った文字が見つかった時点で探索を打ち切ることである。
Abstract. An algorithm is presented which finds all occurrences of one given string within another, in running time proportional to the sum of the lengths of the strings. The constant of proportionality is low enough to make this algorithm of practical use, and the procedure can also be extended to deal with some more general pattern-matching problems. A theoretical application of the algorithm shows that the set of concatenations of even palindromes, i.e., the language , can be recognized in linear time. Other algorithms which run even faster on the average are also considered.
Key words. pattern, string, text-editing, pattern-matching, trie memory, searching, period of a string, palindrome, optimum algorithm, Fibonacci string, regular expression
Text-editing programs are often required to search through a string of characters looking for instances of a given "pattern" string; we wish to find all positions, or perhaps only the leftmost position, in which the pattern occurs as a contiguous substring of the text. For example, catenary contains the pattern ten, but we do not regard canary as a substring.
The obvious way to search for a matching pattern is to try searching at every starting position of the text, abandoning the search as soon as an incorrect character is found. But this approach can be very inefficient, for example when we are looking for an occurrence of in . When the pattern is and the text is , we will find ourselves making comparisons of characters. Furthermore, the traditional approach involves "backing up" the input text as we go through it, and this can add annoying complications when we consider the buffering operations that are frequently involved.
In this paper we describe a pattern-matching algorithm which finds all occurrences of a pattern of length within a text of length in units of time, without "backing up" the input text. The algorithm needs only locations of internal memory if the text is read from an external file, and only units of time elapse between consecutive single-character inputs. All of the constants of proportionality implied by these "O" formulas are independent of the alphabet size.
* Received by the editors August 29, 1974, and in revised form April 7, 1976. † Computer Science Department, Stanford University, Stanford, California 94305. The work of this author was supported in part by the National Science Foundation under Grant GJ 36473X and by the Office of Naval Research under Contract NR 044-402. ‡ Xerox Palo Alto Research Center, Palo Alto, California 94304. The work of this author was supported in part by the National Science Foundation under Grant GP 7635 at the University of California, Berkeley. ¶ Artificial Intelligence Laboratory, Massachusetts Institute of Technology, Cambridge, Massachusetts 02139. The work of this author was supported in part by the National Science Foundation under Grant GP-6945 at University of California, Berkeley, and under Grant GJ-992 at Stanford University.
We shall first consider the algorithm in a conceptually simple but somewhat inefficient form. Sections 3 and 4 of this paper discuss some ways to improve the efficiency and to adapt the algorithm to other problems. Section 5 develops the underlying theory, and § 6 uses the algorithm to disprove the conjecture that a certain context-free language cannot be recognized in linear time. Section 7 discusses the origin of the algorithm and its relation to other recent work. Finally, § 8 discusses still more recent work on pattern matching.
- Informal development. The idea behind this approach to pattern matching is perhaps easiest to grasp if we imagine placing the pattern over the text and sliding it to the right in a certain way. Consider for example a search for the pattern in the text ; initially we place the pattern at the extreme left and prepare to scan the leftmost character of the input text:
↑
The arrow here indicates the current text character; since it points to , which doesn’t match that , we shift the pattern one space right and move to the next input character:
↑
Now we have a match, so the pattern stays put while the next several characters are scanned. Soon we come to another mismatch:
↑
At this point we have matched the first three pattern characters but not the fourth, so we know that the last four characters of the input have been where ; we don’t have to remember the previously scanned characters, since our position in the pattern yields enough information to recreate them. In this case, no matter what is (as long as it’s not ), we deduce that the pattern can immediately be shifted four more places to the right; one, two, or three shifts couldn’t possibly lead to a match.
Soon we get to another partial match, this time with a failure on the eighth pattern character:
Now we know that the last eight characters were , where . The pattern should therefore be shifted three places to the right:
We try to match the new pattern character, but this fails too, so we shift the pattern four (not three or five) more places. That produces a match, and we continue scanning until reaching another mismatch on the eighth pattern character:
Again we shift the pattern three places to the right; this time a match is produced, and we eventually discover the full pattern:
The play-by-play description for this example indicates that the pattern-matching process will run efficiently if we have an auxiliary table that tells us exactly how far to slide the pattern, when we detect a mismatch at its th character . Let be the character position in the pattern which should be checked next after such a mismatch, so that we are sliding the pattern places relative to the text. The following table lists the appropriate values:
(Note that means that we are to slide the pattern all the way past the current text character.) We shall discuss how to precompute this table later; fortunately, the calculations are quite simple, and we will see that they require only steps.
At each step of the scanning process, we move either the text pointer or the pattern, and each of these can move at most times; so at most steps need to be performed, after the table has been set up. Of course the pattern itself doesn’t really move; we can do the necessary operations simply by maintaining the pointer variable .
- Programming the algorithm. The pattern-match process has the general form
place pattern at left;
while pattern not fully matched
and text not exhausted do
begin
while pattern character differs from current text character
do shift pattern appropriately;
advance to next character of text;
end;
For convenience, let us assume that the input text is present in an array , and that the pattern appears in . We shall also assume that , i.e., that the pattern is nonempty. Let and be integer variables such that denotes the current text character and denotes the corresponding pattern character; thus, the pattern is essentially aligned with positions through of the text, where . Then the above program takes the following simple form:
while j ≤ m and k ≤ n do
begin
while j > 0 and text[k] ≠ pattern[j]
do j := next[j];
k := k + 1; j := j + 1;
end;
If at the conclusion of the program, the leftmost match has been found in positions through ; but if , the text has been exhausted. (The and operation here is the “conditional and” which does not evaluate the relation unless .) The program has a curious feature, namely that the inner loop operation “” is performed no more often than the outer loop operation “”; in fact, the inner loop is usually performed somewhat less often, since the pattern generally moves right less frequently than the text pointer does.
To prove rigorously that the above program is correct, we may use the following invariant relation: “Let (i.e., the position in the text just preceding the first character of the pattern, in our assumed alignment). Then we have for (i.e., we have matched the first characters of the pattern, if ); but for we have for some , where (i.e., there is no possible match of the entire pattern to the left of ).”
The program will of course be correct only if we can compute the table so that the above relation remains invariant when we perform the operation . Let us look at that computation now. When the program sets j := next[j], we know that j > 0, and that the last j characters of the input up to and including text[k] were
where . What we want is to find the least amount of shift for which these characters can possibly match the shifted pattern; in other words, we want next[j] to be the largest i less than j such that the last i characters of the input were
and . (If no such i exists, we let next[j] = 0.) With this definition of next[j] it is easy to verify that text[t+1] ... text[k] \neq pattern[1] \ldots pattern[k-1] for k - j \leq t < k - next[j]; hence the stated relation is indeed invariant, and our program is correct.
Now we must face up to the problem we have been postponing, the task of calculating next[j] in the first place. This problem would be easier if we didn't require in the definition of next[j], so we shall consider the easier problem first. Let f[j] be the largest i less than j such that ; since this condition holds vacuously for , we always have when . By convention we let .
The pattern used in the example of § 1 has the following f table:
If then ; but if not, we can use essentially the same pattern-matching algorithm as above to compute , with (Note the similarity of the problem to the invariant condition of the matching algorithm. Our program calculates the largest j less than or equal to k such that , so we can transfer the previous technology to the present problem.) The following program will compute , assuming that and , ..., have already been calculated:
The correctness of this program is demonstrated as before; we can imagine two copies of the pattern, one sliding to the right with respect to the other. For example, suppose we have established that in the above case; let us consider the computation of . The appropriate picture is
Since , we shift the upper copy right, knowing that the most recently scanned characters of the lower copy were for . The table tells us to shift right four places, obtaining
and again there is no match. The next shift makes , so .
Once we understand how to compute , it is only a short step to the computation of . A comparison of the definitions shows that, for ,
Therefore we can compute the table as follows, without ever storing the values of in memory.
while j < m do
begin comment t = f[j];
while t > 0 and pattern[j] ≠ pattern[t]
do t := next[t];
t := t + 1; j := j + 1;
if pattern[j] = pattern[t]
then next[j] := next[t]
else next[j] := t;
end.
This program takes units of time, for the same reason as the matching program takes : the operation in the innermost loop always shifts the upper copy of the pattern to the right, so it is performed a total of times at most. (A slightly different way to prove that the running time is bounded by a constant times is to observe that the variable starts at 0 and it is increased, times, by 1; furthermore its value remains nonnegative. Therefore the operation , which always decreases , can be performed at most times.)
To summarize what we have said so far: Strings of text can be scanned efficiently by making use of two ideas. We can precompute “shifts”, specifying how to move the given pattern when a mismatch occurs at its th character; and this precomputation of “shifts” can be performed efficiently by using the same principle, shifting the pattern against itself.
- Gaining efficiency. We have presented the pattern-matching algorithm in a form that is rather easily proved correct; but as so often happens, this form is not very efficient. In fact, the algorithm as presented above would probably not be competitive with the naive algorithm on realistic data, even though the naive algorithm has a worst-case time of order times instead of plus , because the chance of this worst case is rather slim. On the other hand, a well-implemented form of the new algorithm should go noticeably faster because there is no backing up after a partial match.
It is not difficult to see the source of inefficiency in the new algorithm as presented above: When the alphabet of characters is large, we will rarely have a partial match, and the program will waste a lot of time discovering rather awkwardly that for . When and , the algorithm sets , then discovers that , then increases by 1, then sets to 1 again, then tests whether or not 1 is , and later it tests whether or not 1 is greater than 0. Clearly we would be much better off making into a special case.
The algorithm also spends unnecessary time testing whether or . A fully-matched pattern can be accounted for by setting for some impossible character @ that will never be matched, and by letting ; then a test for can be inserted into a less-frequently executed part of the code. Similarly we can for example set (another impossible character) and , so that the test for needn’t be made very often. (See [17] for a discussion of such more or less mechanical transformations on programs.)
The following form of the algorithm incorporates these refinements.
a := pattern[1];
pattern[m + 1] := '@'; next[m + 1] := -1;
text[n + 1] := '\bot'; text[n + 2] := a;
j := k := 1;
get started: comment j = 1;
while text[k] ≠ a do k := k + 1;
if k > n then go to input exhausted;
char matched: j := j + 1; k := k + 1;
loop: comment j > 0;
if text[k] = pattern[j] then go to char matched;
j := next[j];
if j = 1 then go to get started;
if j = 0 then
begin
j := 1; k := k + 1;
go to get started;
end;
if j > 0 then go to loop;
comment text[k - m] through text[k - 1] matched;
This program will usually run faster than the naive algorithm; the worst case occurs when trying to find the pattern in a long string of 's. Similar ideas can be used to speed up the program which prepares the table.
In a text-editor the patterns are usually short, so that it is most efficient to translate the pattern directly into machine-language code which implicitly contains the table (cf. [3, Hack 179] and [24]). For example, the pattern in § 1 could be compiled into the machine-language equivalent of
L0: ; L1: L2: L3: L4: L5: L6: L7: L8: L9: L10:
This will be slightly faster, since it essentially makes a special case for all values of j.
It is a curious fact that people often think the new algorithm will be slower than the naive one, even though it does less work. Since the new algorithm is conceptually hard to understand at first, by comparison with other algorithms of the same length, we feel somehow that a computer will have conceptual difficulties too—we expect the machine to run more slowly when it gets to such subtle instructions!
4. Extensions. So far our programs have only been concerned with finding the leftmost match. However, it is easy to see how to modify the routine so that all matches are found in turn: We can calculate the next table for the extended pattern of length m + 1 using pattern[m + 1] = "@", and then we set resume := next[m + 1] before setting next[m + 1] to −1. After finding a match and doing whatever action is desired to process that match, the sequence
will restart things properly. (We assume that text has not changed in the meantime. Note that resume cannot be zero.)
Another approach would be to leave next[m + 1] untouched, never changing it to −1, and to define integer arrays head[1 : m] and link[1 : n] initially zero, and to insert the code
at label “char matched”. The test “if then” is also removed from the program. This forms linked lists for of all places where the first characters of the pattern (but no more than ) are matched in the input.
Still another straightforward modification will find the longest initial match of the pattern, i.e., the maximum such that occurs in .
In practice, the text characters are often packed into words, with say characters per word, and the machine architecture often makes it inconvenient to access individual characters. When efficiency for large is important on such machines, one alternative is to carry out independent searches, one for each possible alignment of the pattern’s first character in the word. These searches can treat entire words as “supercharacters”, with appropriate masking, instead of working with individual characters and unpacking them. Since the algorithm we have described does not depend on the size of the alphabet, it is well suited to this and similar alternatives.
Sometimes we want to match two or more patterns in sequence, finding an occurrence of the first followed by the second, etc.; this is easily handled by consecutive searches, and the total running time will be of order plus the sum of the individual pattern lengths.
We might also want to match two or more patterns in parallel, stopping as soon as any one of them is fully matched. A search of this kind could be done with multiple next and pattern tables, with one pointer for each; but this would make the running time plus the sum of the pattern lengths, when there are patterns. Hopcroft and Karp have observed (unpublished) that our pattern-matching algorithm can be extended so that the running time for simultaneous searches is proportional simply to , plus the alphabet size times the sum of the pattern lengths. The patterns are combined into a “trie” whose nodes represent all of the initial substrings of one or more patterns, and whose branches specify the appropriate successor node as a function of the next character in the input text. For example, if there are four patterns , the trie is shown in Fig. 1.
| node | substring | if | if | if |
|---|---|---|---|---|
| 0 | 1 | 7 | 0 | |
| 1 | 1 | 2 | 0 | |
| 2 | 5 | 10 | 3 | |
| 3 | 4 | 7 | 0 | |
| 4 | 1 | |||
| 5 | 1 | 6 | 0 | |
| 6 | 5 | 10 | ||
| 7 | 1 | 10 | 8 | |
| 8 | 9 | 7 | 0 | |
| 9 | 1 | 2 | ||
| 10 | 1 | 10 |
Fig. 1
Such a trie can be constructed efficiently by generalizing the idea we used to calculate next; details and further refinements have been discussed by Aho and Corasick [2], who discovered the algorithm independently. (Note that this
Therefore if we have a mismatch when , our algorithm might set for the successive values 20, 12, 7, 4, 2, 1, 0 of . Since is rounded to the nearest integer, it is possible to have up to consecutive iterations of the loop.
We shall now show that Fibonacci strings actually are the worst case, i.e., that is also an upper bound. First let us consider the concept of periodicity in strings. We say that is a period of if
It is easy to see that is a period of if and only if
for some , where and . Equivalently, is a period of if and only if
for some and with . Condition (6) implies (7) with and . Condition (7) implies (6), for we define and observe that if , then implies and ; hence, reasoning inductively, for some with , and . Writing yields (6).
The relevance of periodicity to our algorithm is clear once we consider what it means to shift a pattern. If ends with , we have
where , so the amount of shift is a period of .
The construction of in our algorithm implies further that , which is the first character of , is unequal to . Let us assume that itself is subsequently shifted leaving a residue , so that
where the first character of differs from that of . We shall now prove that
If , there is an overlap of characters between the occurrences of and in ; hence the first character of is . Similarly there is an overlap of characters between the occurrences of and in ; hence the first character of is . Since these characters are distinct, we obtain , contradicting (9). This establishes (10), and leads directly to the announced result:
Theorem. The number of consecutive times that is performed, while one text character is being scanned, is at most .
Proof. Let be the length of the shortest string as in the above discussion such that a sequence of consecutive shifts is possible. Then , and we have in (10); hence by induction on . Now if shifts occur we have .
The algorithm of § 2 would run correctly in linear time even if were used instead of , but the analogue of the above theorem would then be false. For example, the pattern leads to for . Therefore if we matched to the text , using instead of , the mismatch would be followed by occurrences of and redundant comparisons of with , before is advanced to .
The subject of periods in strings has several interesting algebraic properties, but a reader who is not mathematically inclined may skip to § 6 since the following material is primarily an elaboration of some additional structure related to the above theorem.
Lemma 1. If p and q are periods of , and , then is a period of .
Proof. Let , and assume without loss of generality that . We have for and for ; hence for , i.e.,
Furthermore where , and it follows that and are periods of , where . By induction, is a period of . Since , the strings and (which have the respective forms and by (6) and (7)) are substrings of ; so they also have as a period. The string must now have as a period, since any characters positions apart are contained within or .
The result of Lemma 1 but with the stronger hypothesis was proved by Lyndon and Schützenberger in connection with a problem about free groups [19, Lem. 4]. The weaker hypothesis in Lemma 1 turns out to give the best possible bound: If we can find a string of length for which is not a period. In order to see why this is so, consider first the example in Fig. 2 showing the most general strings of lengths 15 through 25 having both 11 and 15 as periods. (The strings are “most general” in the sense that any two character positions that can be different are different.)
abcdefghijklabcd abcdafghijklabcda abcdabghijklabcdab abcdabchijklabcdabc abcdabcdijklabcdabcd abcdabcdajklabcdabcd abcdabcdabkabcdabcdab abcdabcdabcabcdabc abcabcaabcabcabcabcabc aacaaacaaacaaacaaacaaacaa aaaaaaaaaaaaaaaaaaaaaaa
Fig. 2
Note that the number of degrees of freedom, i.e., the number of distinct symbols, decreases by 1 at each step. It is not difficult to prove that the number cannot decrease by more than 1 as we go from to , since the only new relations are ; we decrease the number of distinct symbols by one if and only if positions and contain distinct symbols in the most general string of length . The lemma tells us that we are left with at most symbols when the length reaches ; on the other hand we always have exactly symbols when the length is . Therefore each of the steps must decrease the number of symbols by 1, and the most general string of length must have exactly distinct symbols. In other words, the lemma gives the best possible bound.
When and are relatively prime, the strings of length on two symbols, having both and as periods, satisfy a number of remarkable properties, generalizing what we have observed earlier about Fibonacci strings. Since the properties of these pathological patterns may prove useful in other investigations, we shall summarize them in the following lemma.
Lemma 2. Let the strings of length be defined for all relatively prime pairs of integers as follows:
if .
These strings satisfy the following properties: (i) for ; (ii) has period , for ; (iii) , for . (The function was defined in connection with (3) above.)
Proof. We have, for and ,
hence, if and and ,
It follows that
which combine to prove (i). Property (ii) also follows immediately from (12), except for the case , which may be verified directly. Finally, it suffices to verify property (iii) for , since ; we must show that
When this property is easily checked, and when it is equivalent by induction to
Set , and apply property (i).
By properties (ii) and (iii) of this lemma, minus its last two characters is the string of length having periods and . Note that Fibonacci strings are just a very special case, since . Another property of the strings appears in [15]. A completely different proof of Lemma 1 and its optimality, and a completely different definition of , were given by Fine and Wilf in 1965 [7]. These strings have a long history going back at least to the astronomer Johann Bernoulli in 1772; see [25, § 2.13] and [21].
If is any string, let be its shortest period. Lemma 1 implies that all periods which are not multiples of must be greater than . This is a rather strong condition in terms of the pattern matching algorithm, because of the following result.
Lemma 3. Let and let . In the pattern matching algorithm, , and , where is the smallest period of which is not a period of . (If no such period exists, .) If divides and , then . If does not divide or if , then .
Proof. The characterizations of and follow immediately from the definitions. Since every period of is a period of , the only nonobvious statement is that whenever divides and . Let and ; then the th character from the right of is , as is the th, , as is the th; hence is a period of .
Lemma 3 shows that the loop will almost always terminate quickly. If , then must not be a multiple of ; hence by Lemma 1, . On the other hand ; hence and . In the other case , we had better not have too small, since will be a period in the residual pattern after shifting, and will be . To keep the loop running it is necessary for new small periods to keep popping up, relatively prime to the previous periods.
- Palindromes. One of the most outstanding unsolved questions in the theory of computational complexity is the problem of how long it takes to determine whether or not a given string of length belongs to a given context-free language. For many years the best upper bound for this problem was in a general context-free language as ; L. G. Valiant has recently lowered this to . On the other hand, the problem isn’t known to require more than order units of time for any particular language. This big gap between and deserves to be closed, and hardly anyone believes that the final answer will be .
Let be a finite alphabet, let denote the strings over , and let
Here denotes the reversal of , i.e., . Each string in is a palindrome of even length, and conversely every even palindrome over
Σ is in P. At one time it was popularly believed that the language of “even palindromes starred”, namely the set of palstars where each is in , would be impossible to recognize in steps on a random-access computer.
It isn’t especially easy to spot members of this language. For example, is a palstar, but its decomposition into even palindromes might not be immediately apparent; and the reader might need several minutes to decide whether or not
is in . We shall prove, however, that palstars can be recognized in units of time, by using their algebraic properties.
Let us say that a nonempty palstar is prime if it cannot be written as the product of two nonempty palstars. A prime palstar must be an even palindrome but the converse does not hold. By repeated decomposition, it is easy to see that every palstar is expressible as a product of prime palstars, for some ; what is less obvious is that such a decomposition into prime factors is unique. This “fundamental theorem of palstars” is an immediate consequence of the following basic property.
Lemma 1. A prime palstar cannot begin with another prime palstar.
Proof. Let be a prime palstar such that for some nonempty even palindrome and some ; furthermore, let have minimum length among all such counterexamples. If then for some ; hence , and , contradicting the minimality of . Therefore ; hence for some , and . But this implies that is the palstar , contradicting the primality of .
Corollary (Left cancellation property.) If and are palstars, so is .
Proof. Let and be prime factorizations of and . If , then is a palstar. Otherwise let be minimal with ; then begins with or vice versa, contradicting Lemma 1.
Lemma 2. If is a string of length , we can determine the length of the longest even palindrome such that , in steps.
Proof. Apply the pattern-matching algorithm with pattern = and text = . When the algorithm will stop with maximal such that . Now perform the following iteration:
while and even do .
By the theory developed in § 3, this iteration terminates with if and only if begins with a nonempty even palindrome, and will be the length of the largest such palindrome. (Note that must be used here instead of next[j]; e.g. consider the case . But the pattern matching process takes time even when is used.)
Theorem. Let be any language such that has the left cancellation property and such that, given any string of length , we can find a nonempty such that begins with or we can prove that no such exists, in steps. Then we can determine in time whether or not a given string is in .
Proof. Let be any string, and suppose that the time required to test for nonempty prefixes in is for all large . We begin by testing 's initial subsequences of lengths , and finally itself, until finding a prefix in or until establishing that has no such prefix. In the latter case, is not in , and we have consumed at most units of time for some constant . But if we find a nonempty prefix where , we have used at most units of time so far. By the left cancellation property, if and only if , and since we can prove by induction that at most units of time are needed to decide membership in , when .
Corollary. can be recognized in time.
Note that the related language
cannot be handled by the above techniques, since it contains both and ; the fundamental theorem of palstars fails with a vengeance. It is an open problem whether or not can be recognized in time, although we suspect that it can be done.\footnote{(Note added April, 1976.) Zvi Galil and Joel Seiferas have recently resolved this conjecture affirmatively.} Once the reader has disposed of this problem, he or she is urged to tackle another language which has recently been introduced by S. A. Greibach [11], since the latter language is known to be as hard as possible; no context-free language can be harder to recognize except by a constant factor.
- Historical remarks. The pattern-matching algorithm of this paper was discovered in a rather interesting way. One of the authors (J. H. Morris) was implementing a text-editor for the CDC 6400 computer during the summer of 1969, and since the necessary buffering was rather complicated he sought a method that would avoid backing up the text file. Using concepts of finite automata theory as a model, he devised an algorithm equivalent to the method presented above, although his original form of presentation made it unclear that the running time was . Indeed, it turned out that Morris’s routine was too complicated for other implementors of the system to understand, and he discovered several months later that gratuitous “fixes” had turned his routine into a shambles.
In a totally independent development, another author (D. E. Knuth) learned early in 1970 of S. A. Cook’s surprising theorem about two-way deterministic pushdown automata [5]. According to Cook’s theorem, any language recognizable by a two-way deterministic pushdown automaton, in any amount of time, can be recognized on a random access machine in units of time. Since D. Chester had recently shown that the set of strings beginning with an even palindrome could be recognized by such an automaton, and since Knuth couldn’t imagine how to recognize such a language in less than about steps on a conventional computer, Knuth laboriously went through all the steps of Cook’s construction as applied to Chester’s automaton. His plan was to “distill off” what was happening, in order to discover why the algorithm worked so efficiently. After pondering the mass of details for several hours, he finally succeeded in abstracting the mechanism which seemed to be underlying the construction, in the special case of palindromes, and he generalized it slightly to a program capable of finding the longest prefix of one given string that occurs in another.
This was the first time in Knuth’s experience that automata theory had taught him how to solve a real programming problem better than he could solve it before. He showed his results to the third author (V. R. Pratt), and Pratt modified Knuth’s data structure so that the running time was independent of the alphabet size. When Pratt described the resulting algorithm to Morris, the latter recognized it as his own, and was pleasantly surprised to learn of the time bound, which he and Pratt described in a memorandum [22]. Knuth was chagrined to learn that Morris had already discovered the algorithm, without knowing Cook’s theorem; but the theory of finite-state machines had been of use to Morris too, in his initial conceptualization of the algorithm, so it was still legitimate to conclude that automata theory had actually been helpful in this practical problem.
The idea of scanning a string without backing up while looking for a pattern, in the case of a two-letter alphabet, is implicit in the early work of Gilbert [10] dealing with comma-free codes. It also is essentially a special case of Knuth’s LR(0) parsing algorithm [16] when applied to the grammar
where is the pattern. Diethelm and Roizen [6] independently discovered the idea in 1971. Gilbert and Knuth did not discuss the preprocessing to build the next table, since they were mainly concerned with other problems, and the pre-processing algorithm given by Diethelm and Roizen was of order . In the case of a binary (two-letter) alphabet, Diethelm and Roizen observed that the algorithm of § 3 can be improved further: we can go immediately to “char matched” after in this case if .
A conjecture by R. L. Rivest led Pratt to discover the upper bound on pattern movements between successive input characters, and Knuth showed that this was best possible by observing that Fibonacci strings have the curious properties proved in § 5. Zvi Galil has observed that a real-time algorithm can be obtained by letting the text pointer move ahead in an appropriate manner while the pointer is moving down [9].
In his lectures at Berkeley, S. A. Cook had proved that was recognizable in steps on a random-access machine, and Pratt improved this to using a preliminary form of the ideas in § 6. The slightly more refined theory in the present version of § 6 is joint work of Knuth and Pratt. Manacher [20] found another way to recognize palindromes in linear time, and Galil [9] showed how to improve this to real time. See also Slisenko [23].
It seemed at first that there might be a way to find the longest common substring of two given strings, in time ; but the algorithm of this paper does not readily support any such extension, and Knuth conjectured in 1970 that such efficiency would be impossible to achieve. An algorithm due to Karp, Miller, and Rosenberg [13] solved the problem in steps, and this tended to support the conjecture (at least in the mind of its originator). However, Peter Weiner has recently developed a technique for solving the longest common substring problem in units of time with a fixed alphabet, using tree structures in a remarkable new way [26]. Furthermore, Weiner’s algorithm has the following interesting consequence, pointed out by E. McCreight: a text file can be processed (in linear time) so that it is possible to determine exactly how much of a pattern is necessary to identify a position in the text uniquely; as the pattern is being typed in, the system can be interrupt as soon as it “knows” what the rest of the pattern must be! Unfortunately the time and space requirements for Weiner’s algorithm grow with increasing alphabet size.
If we consider the problem of scanning finite-state languages in general, it is known [1 § 9.2] that the language defined by any regular expression of length is recognizable in units of time. When the regular expression has the form
the algorithm we have discussed shows that only units of time are needed (considering as a character of length 1 in the expression). Recent work by M. J. Fischer and M. S. Paterson [8] shows that regular expressions of the form
i.e., patterns with “don’t care” symbols, can be identified in units of time, where is the alphabet size and . The constant of proportionality in their algorithm is extremely large, but the existence of their construction indicates that efficient new algorithms for general pattern matching problems probably remain to be discovered.
A completely different approach to pattern matching, based on hashing, has been proposed by Malcolm C. Harrison [12]. In certain applications, especially with very large text files and short patterns, Harrison’s method may be significantly faster than the character-comparing method of the present paper, on the average, although the redundancy of English makes the performance of his method unclear.
- Postscript: Faster pattern matching in strings. In the spring of 1974, Robert S. Boyer and J. Strother Moore and (independently) R. W. Gosper noticed that there is an even faster way to match pattern strings, by skipping more rapidly over portions of the text that cannot possibly lead to a match. Their idea was to look first at , instead of . If we find that the character does not appear in the pattern at all, we can immediately shift the pattern right places. Thus, when the alphabet size is large, we need to inspect only about characters of the text, on the average! Furthermore if does occur in the pattern, we can shift the pattern by the minimum amount consistent with a match.
\footnotetext{ This postscript was added by D. E. Knuth in March, 1976, because of developments which occurred after preprints of this paper were distributed.
Several interesting variations on this strategy are possible. For example, if does occur in the pattern, we might continue the search by looking at , etc.; in a random file we will usually find a small value of such that the substring does not appear in the pattern, so we can shift the pattern places. If , there are more than possible values of , but only substrings of length in the pattern, hence the probability is that occurs in the pattern; If it doesn’t, we can shift the pattern right places; but if it does, we can determine all matches in positions in steps, shifting the pattern places by the method of this paper. Hence the expected number of characters examined among the first is ; this proves the existence of a linear worst-case algorithm which inspects characters in a random text. This upper bound on the average running time applies to all patterns, and there are some patterns (e.g., or ) for which the expected number of characters examined by the algorithm is .
Boyer and Moore have refined the skipping-by- idea in another way. Their original algorithm may be expressed as follows using our conventions:
This program calls match found at for all such that . There are two precomputed tables, namely
for each of the possible characters , and
for .
The table can clearly be set up in steps, and the table can be precomputed in steps using a technique analogous to the method in § 2 above, as we shall see. The Boyer–Moore paper [4] contains further exposition of the algorithm, including suggestions for highly efficient implementation, and gives both theoretical and empirical analyses. In the remainder of this section we shall show how the above methods can be used to resolve some of the problems left open in [4].
First let us improve the original Boyer–Moore algorithm slightly by replacing by
(This is analogous to using instead of ; Boyer and Moore [4] credit the improvement in this case to Ben Kuipers, but they do not discuss how to determine efficiently.) The following program shows how the table can be precomputed in steps; for purposes of comparison, the program also shows how to compute , which actually turns out to require slightly more operations than :
for k := 1 step 1 until m do dd[k] := dd'[k] := 2 × m - k;
j := m; t := m + 1;
while j > 0 do
begin
f[j] := t;
while t ≤ m and pattern[j] ≠ pattern[t] do
begin
dd'[t] := min (dd'[t], m - j);
t := f[t];
end;
t := t - 1; j := j - 1;
dd[t] := min (dd[t], m - j);
end;
for k := 1 step 1 until t do
begin
dd[k] := min (dd[k], m + t - k);
dd'[k] := min (dd'[k], m + t - k);
end;
In practice one would, of course, compute only , suppressing all references to . The example in Table 2 illustrates most of the subtleties of this algorithm.
| Table 2 | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| j=1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| pattern[j]=b | a | d | b | a | c | b | a | c | b | a |
| f[j]=10 | 11 | 6 | 7 | 8 | 9 | 10 | 11 | 11 | 11 | 12 |
| dd[j]=19 | 18 | 17 | 16 | 15 | 8 | 7 | 6 | 5 | 4 | 1 |
| dd'[j]=19 | 18 | 17 | 16 | 15 | 8 | 13 | 12 | 8 | 12 | 1 |
To prove correctness, one may show first that is analogous to the in § 2, but with right and left of the pattern reversed; namely , and for we have
Furthermore the final value of corresponds to in this definition; is the maximum overlap of the pattern on itself. The correctness of and for all now follows without much difficulty, by showing that the minimum value of in the definition of or is discovered by the algorithm when .
The Boyer–Moore algorithm and its variants can have curiously anomalous behavior in unusual circumstances. For example, the method discovers more quickly that the pattern does not appear in the text if it suppresses the heuristic entirely, i.e., if is set to for all . Likewise, actually turns out to be better than when matching in , for large .
Boyer and Moore showed that their algorithm has quadratic behavior in the worst case; the running time can be essentially proportional to pattern length times text length, for example when the pattern occurs together with the text . They observed that this particular example was handled in linear time when Kuiper’s improvement ( for ) was made; but they left open the question of the true worst case behavior of the improved algorithm.
There are trivial cases in which the Boyer–Moore algorithm has quadratic behavior, when matching all occurrences of the pattern, for example when matching the pattern in the text . But we are probably willing to accept such behavior when there are so many matches; the crucial issue is how long the algorithm takes in the worst case to scan over a text that does not contain the pattern at all. By extending the techniques of § 5, it is possible to show that the modified Boyer–Moore algorithm is linear in such a situation:
Theorem. If the above algorithm is used with dd' replacing dd, and if the text does not contain any occurrences of the pattern, the total number of characters matched is at most 6n.
Proof. An execution of the algorithm consists of a series of stages, in which characters are matched and then the pattern is shifted places, for . We want to show that ; the proof is based on breaking this cost into three parts, two of which are trivially and the third of which is less obviously so.
Let if ; otherwise let . When , we will say that the leftmost text characters matched during the th stage have been “tapped”. It suffices to prove that the algorithm taps characters at most times, since and . Unfortunately it is possible for some characters of the text to be tapped roughly times, so we need to argue carefully that .
Suppose the rightmost of the text characters tapped during the th stage are matched again during some later stage, but the leftmost are being matched for the last time. Clearly , so it remains to show that .
Let be the amount by which the pattern would shift after the th stage if the heuristic were not present (); then , and is a period of the string matched at stage .
Consider a value of such that , and suppose that the text characters matched during the th stage form the string where and ; hence the text characters in are matched for the last time. Since the pattern does not occur in the text, it must end with and the text scanned so far must end with , where . At this point the algorithm will shift the pattern right positions and will enter stage . We distinguish two cases: (i) The pattern length exceeds . Then the pattern can be written , where ; the last character of is and the last character of is , by definition of . Otherwise (ii) ; the pattern then has the form , where . By definition of and the assumption that the pattern does not occur in the text, we have , i.e., . In both cases (i) and (ii), is a period of .
Now consider the first subsequent stage during which the leftmost of the text characters tapped during stage is matched again; we shall write when the stages are in this relation. Suppose the mismatch occurs this time when text character fails to match pattern character . If occurs in the text within , regarding as fixed in its stage position, then cannot be within where now occurs in the stage position of the pattern, since is a period of and the character positions to the right of is a (it matches a in the text). Thus now appears within . On the other hand, if occurs to the left of , we must have , since the characters of are never matched again. In either event, case (ii) above proves to be impossible. Hence case (i) always occurs when , and always appears within .
To complete the argument, we shall show that , for all fixed , is at most . Let and let denote the pattern matched at stage . Let be the values of such that . If , let be the rightmost characters of the pattern. Otherwise let be the leftmost characters of ; and let be followed by the pattern. Note that in both cases is an initial substring of and . In both cases, the actions of the algorithm during stages through are completely known if we are given the pattern and , and if we know and the place within where stage starts matching. This follows from the fact that by itself determines the text, so that if we match the pattern against the string (starting at the specified place for stage ) until the algorithm first tries to match we will know the length of . (If then begins with and this statement holds trivially; otherwise, begins with and has period ; hence begins with .) Note that the algorithm cannot begin two different stages at exactly the same position within , for then it would loop indefinitely, contradicting the fact that it does terminate. This property will be out key tool for proving the desired result.
Let the text strings matched during stages be , and let their periods determined as in case (i) be respectively; we have for . Suppose that during stage the mismatch of implies that the pattern ends with , where . We shall prove that . First let us prove that for all : We have observed that always occurs within ; hence occurs as a rightmost substring of . If then ; hence the character positions to the right of in is , as is the character positions to the right of . But the character positions to the right of in is a , since is a period of ; hence the character positions to the right of is also , contradicting .
Since , each string for appears somewhere within , when is regarded as a cyclic string, joined end-for-end. (It follows from the definition of that is a substring of for .) We shall prove that the rightmost halves of these strings, namely the rightmost characters as they appear in , are disjoint. This implies that , and the proof will be complete (since ).
Suppose therefore that the right half of the appearance of overlaps the right half of the appearance of within , for some , where the rightmost character of is within . This means that the algorithm at stage begins to match characters starting within at least characters to the right of where appears in , when the text is treated modulo . (Recall that .) The pattern ends with , and is a period of . The algorithm must work correctly when the text equals the pattern, so there must come a stage, before shifting the pattern to the right of the appearance of , where the algorithm scans left until hitting . At this point, call it stage , there must be a mismatch of , since or more characters have been matched. (The character positions to the right of is , by periodicity.) Hence ; and it follows that . (If we have entirely contained within , but then implies that .) Now implies that and . We shall obtain a contradiction by showing that the algorithm "synchronizes" its stage behavior with its stage behavior, modulo , causing an infinite loop as remarked above. The main point is that the table will specify shifting the pattern steps, so that is brought into the position corresponding to , in stage as well as in stage . (Any lesser shift brings an into position spaces to the right of ; hence it puts into the position corresponding to , by periodicity, contradicting .) The amount of shift depends on the maximum of the and entries, and the entry will be chosen (in either or ) if and only if is not a character of ; but in this case, the entry will also specify the same shift both for stage and stage .
The constant 6 in the above theorem is probably much too large, and the above proof seems to be much too long; the reader is invited to improve the theorem in either or both respects. An interesting example of the rather complex behavior possible with this algorithm occurs when the pattern is and the text is for large , where
Corollary. The worst case running time of the Boyer–Moore algorithm with dd' replacing dd is character comparisons, if the pattern occurs r times in the text.
Proof. Let be the worst case running time as a function of and , when is fixed. The theorem implies that , counting the mismatched characters as well as the matched ones. Furthermore, if and if the first appearance of the pattern ends at position we have . It follows that
When the Boyer–Moore algorithm implicitly shifts the pattern to the right, it forgets all it “knows” about characters already matched; this is why the linearity theorem is not trivial. A more complex algorithm can be envisaged, with a finite number of states corresponding to which text characters are known to match the pattern in its current position; when in state we fetch the character , then we set and go to state . For example, consider the pattern , and the specification of and in Table 3; exactly 41 distinguishable states can arise. An asterisk (*) in that table shows where the pattern has been fully matched.
The number of states in this generalization of the Boyer–Moore algorithm can be rather large, as the example shows, but the patterns which occur most often in practice probably do not imply many states. The number of states is always less than , and perhaps a much smaller upper bound is possible; it is unclear which patterns of a given length lead to the most states, and it does not seem obvious that this maximum number of states is exponential in .
If the characters of the pattern are distinct, say , this generalization of the Boyer–Moore algorithm leads to exactly states. (Namely, all states of the form for , with suppressed if .) By merging several of these states we obtain the following simple algorithm, which uses a table where
The algorithm works only when all pattern characters are distinct, but it improves slightly on the Boyer–Moore technique in this important special case.
while k ≤ n do
begin i := c[text[k]];
if i < 0 then j := m
else if i = 0 then
begin for i := 1 step 1 until m - 1 do
if text[k - i] ≠ pattern[m - i] then go to nomatch;
match found at (k - m);
end
nomatch: j := m;
end else if i + j ≥ m then j := i else j := m;
k := k + j;
end;
Let us close this section by making a preliminary investigation into the question of “fastest” pattern matching in strings, i.e., optimum algorithms. What algorithm minimizes the number of text characters examined, over all conceivable algorithms for the problem we have been considering? In order to make this question nontrivial, we shall ask for the minimum average number of characters examined when finding all occurrences of the pattern in the text, where the average is taken uniformly with respect to strings of length over a given alphabet. (The minimum worst case number of characters examined is of no interest, since it is between and for all patterns; therefore we ask for the minimum average number. It might be argued that the minimum average number, taken over random strings, is of little interest, since people rarely search in random strings; they usually search for patterns that actually appear. However, the random-string model is a reasonable approximation when we consider those stretches of text that do not contain the pattern, and the algorithm obviously must examine every character in those places where the pattern does occur.)
The case of patterns of length 2 can be solved exactly; it is somewhat surprising to find that the analysis is not completely trivial even in this case. Consider first the pattern where . Let be the alphabet size, . Let denote the minimum average number of characters examined by an algorithm which finds all occurrences of the pattern in a random text of length ; and let denote the minimum average number of characters examined in a random text of length which is known to begin with , not counting the examination of the known first character. These functions can be computed by the following recurrence relations:
The recurrence for follows by considering which character is examined first; the recurrence for follows from the fact that the second character must be examined in any case, so it can be examined first without loss of efficiency. It can be shown that the minimum is always assumed for ; hence we obtain the closed form solution
(To prove that these functions satisfy the stated recurrences reduces to showing that the minimum of
for occurs for , whenever and .)
\footnotetext{3 This is clear when we must find all occurrences of the pattern; R. L. Rivest has recently proved it also for algorithms which stop after finding one occurrence. (Information Processing Letters, to appear.)}
| state q | known characters | t[q] | s[q, x], q'[q, x] | |||
|---|---|---|---|---|---|---|
| x = a | x = b | x = c | other x | |||
| 0 | ● ● ● ● ● ● ● | 0 | 0, 1 | 1, 8 | 4, 9 | 8, 0 |
| 1 | ● ● ● ● ● ● a | 1 | 7, 10 | 0, 2 | 7, 10 | 7, 10 |
| 2 | ● ● ● ● ● b a | 2 | 0, 3 | 7, 10 | 2, 11 | 7, 10 |
| 3 | ● ● ● ● a b a | 3 | 5, 12 | 0, 4 | 5, 12 | 5, 12 |
| 4 | ● ● ● b a b a | 4 | 5, 12 | 5, 12 | 0, 5 | 5, 12 |
| 5 | ● ● ● c b a b a | 5 | 0, 6 | 5, 12 | 5, 12 | 5, 12 |
| 6 | ● ● a c b a b a | 6 | 5, 12 | 0, 7 | 5, 12 | 5, 12 |
| 7 | ● b a c b a b a | 7 | *5, 12 | 5, 12 | 5, 12 | 5, 12 |
| 8 | ● ● ● ● ● ● b ● | 0 | 0, 2 | 8, 0 | 8, 0 | 8, 0 |
| 9 | ● ● ● c ● ● ● ● | 0 | 0, 13 | 6, 14 | 4, 9 | 8, 0 |
| 10 | a ● ● ● ● ● ● ● | 0 | 0, 15 | 1, 8 | 4, 9 | 8, 0 |
| 11 | ● ● ● c b a ● ● | 0 | 0, 16 | 6, 14 | 8, 0 | 8, 0 |
| 12 | a b a ● ● ● ● ● | 0 | 0, 17 | 3, 18 | 4, 9 | 8, 0 |
| 13 | ● ● ● c ● ● ● a | 1 | 7, 10 | 0, 19 | 7, 10 | 7, 10 |
| 14 | ● b ● ● ● ● ● ● | 0 | 0, 20 | 3, 18 | 4, 9 | 8, 0 |
| 15 | a ● ● ● ● ● ● a | 1 | 7, 10 | 0, 21 | 7, 10 | 7, 10 |
| 16 | ● ● ● c b a ● a | 1 | 7, 10 | 0, 5 | 7, 10 | 7, 10 |
| 17 | a b a ● ● ● ● a | 1 | 7, 10 | 0, 22 | 7, 10 | 7, 10 |
| 18 | ● ● ● ● b ● ● ● | 0 | 0, 23 | 3, 24 | 8, 0 | 8, 0 |
| 19 | ● ● ● c ● ● b a | 2 | 0, 25 | 7, 10 | 7, 10 | 7, 10 |
| 20 | ● b ● ● ● ● ● a | 1 | 7, 10 | 0, 26 | 7, 10 | 7, 10 |
| 21 | a ● ● ● ● ● b a | 2 | 0, 27 | 7, 10 | 2, 11 | 7, 10 |
| 22 | a b a ● ● ● b a | 2 | 0, 28 | 7, 10 | 2, 29 | 7, 10 |
| 23 | ● ● ● ● b ● ● a | 1 | 7, 10 | 0, 30 | 7, 10 | 7, 10 |
| 24 | ● b ● ● b ● ● ● | 0 | 0, 31 | 3, 24 | 8, 0 | 8, 0 |
| 25 | ● ● ● c ● a b a | 3 | 5, 12 | 0, 5 | 5, 12 | 5, 12 |
| 26 | ● b ● ● ● ● b a | 2 | 0, 32 | 7, 10 | 2, 11 | 7, 10 |
| 27 | a ● ● ● ● a b a | 3 | 5, 12 | 0, 33 | 5, 12 | 5, 12 |
| 28 | a b a ● ● a b a | 3 | 5, 12 | 0, 34 | 5, 12 | 5, 12 |
| 29 | a ● ● c b a ● ● | 0 | 0, 35 | 6, 14 | 8, 0 | 8, 0 |
| 30 | ● ● ● ● b ● b a | 2 | 0, 4 | 7, 10 | 7, 10 | 7, 10 |
| 31 | ● b ● ● b ● ● a | 1 | 7, 10 | 0, 36 | 7, 10 | 7, 10 |
| 32 | ● b ● ● ● a b a | 3 | 5, 12 | 0, 37 | 5, 12 | 5, 12 |
| 33 | a ● ● ● b a b a | 4 | 5, 12 | 5, 12 | 0, 38 | 5, 12 |
| 34 | a b a ● b a b a | 4 | 5, 12 | 5, 12 | *5, 12 | 5, 12 |
| 35 | a ● ● c b a ● a | 1 | 7, 10 | 0, 38 | 7, 10 | 7, 10 |
| 36 | ● b ● ● b ● b a | 2 | 0, 37 | 7, 10 | 7, 10 | 7, 10 |
| 37 | ● b ● ● b a b a | 4 | 5, 12 | 5, 12 | 0, 39 | 5, 12 |
| 38 | a ● ● c b a b a | 5 | 0, 40 | 5, 12 | 5, 12 | 5, 12 |
| 39 | ● b ● c b a b a | 5 | 0, 7 | 5, 12 | 5, 12 | 5, 12 |
| 40 | a ● a c b a b a | 6 | 5, 12 | *5, 12 | 5, 12 | 5, 12 |
If the pattern is , the recurrence for changes to
but this is actually no change! Hence the following is an optimum algorithm for all patterns of length 2, in the sense of minimum average text characters inspected to find all matches in a random string:
while k ≤ n do
begin c := text[k];
if c = pattern[2] and text[k-1] = pattern[1]
then match found at (k-2);
while c = pattern[1] do
begin k := k+1; c := text[k];
if c = pattern[2] then match found at (k-2);
end;
k := k+2;
end;
For patterns of length 3 the recurrence relations become more complex; they depend on more than simply the length of the strings and knowledge about characters at the boundaries. The determination of an optimum strategy in this case remains an open problem. The algorithm sketched at the beginning of this section shows that an average of bit inspections suffices over a binary alphabet. Clearly is a lower bound, since the algorithm must inspect at least one bit in any block of consecutive bits. The pattern can be handled with bit inspections on the average; but it seems reasonable to conjecture that patterns of length exist for arbitrarily large , such that an average of at least bits must be inspected for all large . Here denotes a positive constant, independent of and .
Acknowledgment. Robert S. Boyer and J. Strother Moore suggested many important improvements to early drafts of this postscript, especially in connection with errors in the author’s first attempts at proving the linearity theorem.
References
- Alfred V. Aho, John E. Hopcroft and Jeffrey D. Ullman, The Design and Analysis of Computer Algorithms, Addison-Wesley, Reading, Mass., 1974.
- Alfred V. Aho and Margaret J. Corasick, Efficient string matching: An aid to bibliographic search, Comm. ACM, 18 (1975), pp. 333–340.
- M. Beeler, R. W. GOSPER AND R. SCHROEPPEL, HAKMEM, Memo No. 239, M.I.T. Artificial Intelligence Laboratory, Cambridge, Mass., 1972.
- Robert S. Boyer and J. Strother Moore, A fast string searching algorithm, manuscript dated December 29, 1975; Stanford Research Institute, Menlo Park, Calif., and Xerox Palo Alto Research Center, Palo Alto, Calif.
- S. A. Cook, Linear time simulation of deterministic two-way pushdown automata, Information Processing 71, North-Holland, Amsterdam, 1972, pp. 75–80.
- Pascal DiethelM and Peter Roizen, An efficient linear search for a pattern in a string, unpublished manuscript dated April, 1972; World Health Organization, Geneva, Switzerland.
- N. J. Fine and H. S. Wilf, Uniqueness theorems for periodic functions, Proc. Amer. Math. Soc., 16 (1965), pp. 109–114.
- Michael J. Fischer and Michael S. Paterson, String matching and other products, SIAM-AMS Proc., vol. 7, American Mathematical Society, Providence, R.I., 1974, pp. 113–125.
- Zvi Galil, On converting on-line algorithms into real-time and on real-time algorithms for string-matching and palindrome recognition, SIGACT News, 7 (1975), No. 4, pp. 26–30.
- E. N. Gilbert, Synchronization of binary messages, IRE Trans. Information Theory, IT-6 (1960), pp. 470–477.
- Sheila A. Greibach, The hardest context-free language, this Journal, 2 (1973), pp. 304–310.
- Malcolm C. Harrison, Implementation of the substring test by hashing, Comm. ACM, 14 (1971), pp. 777–779.
- Richard M. Karp, Raymond E. Miller and Arnold L. Rosenberg, Rapid identification of repeated patterns in strings, trees, and arrays, ACM Symposium on Theory of Computing, vol. 4, Association for Computing Machinery, New York, 1972, pp. 125–136.
- Donald E. Knuth, Fundamental Algorithms, The Art of Computer Programming, Vol. 1, Addison-Wesley, Reading, Mass., 1968; 2nd edition 1973.
- ———, Sequences with precisely k + 1 k-blocks, Solution to problem E2307, Amer. Math. Monthly, 79 (1972), pp. 773–774.
- ———, On the translation of languages from left to right, Information and Control, 8 (1965), pp. 607–639.
- ———, Structured programming with go to statements, Computing Surveys, 6 (1974), pp. 261–301.
- Donald E. Knuth, James H. Morris, Jr. and Vaughan R. Pratt, Fast pattern matching in strings, Tech. Rep. CS440, Computer Science Department, Stanford Univ., Stanford, Calif., 1974.
- R. C. Lyndon and M. P. Schützenberger, The equation $a^M = b^N c^P$ in a free group, Michigan Math. J., 9 (1962), pp. 289–298.
- Glenn Manacher, A new linear-time on-line algorithm for finding the smallest initial palindrome of a string, J. Assoc. Comput. Mach., 22 (1975), pp. 346–351.
- A. Markoff, Sur une question de Jean Bernoulli, Math. Ann., 19 (1882), pp. 27–36.
- J. H. Morris, Jr. and Vaughan R. Pratt, A linear pattern-matching algorithm, Tech. Rep. 40, Univ. of California, Berkeley, 1970.
- A. O. Slisenko, Recognition of palindromes by multihead Turing machines, Dokl. Steklov Math. Inst., Akad. Nauk SSSR, 129 (1973), pp. 30–202. (In Russian.)
- Ken Thompson, Regular expression search algorithm, Comm. ACM, 11 (1968), pp. 419–422.
- B. A. Venkov, Elementary Number Theory, Wolters-Noordhoff, Groningen, the Netherlands, 1970.
- Peter Weiner, Linear pattern matching algorithms, IEEE Symposium on Switching and Automata Theory, vol. 14, IEEE, New York, 1973, pp. 1–11.
Read off the page by a vision model reading the page images using olmOCR-2-7B-1025-FP8. Translated into 日本語 by gpt-5, against glossary version 6. Translated from the English.
Read off the page by a vision model reading the page images using olmOCR-2-7B-1025-FP8.