Saving space in fast string-matching

The inspiration for this paper was an attempt to implement the fast string-matching algorithm of Knuth, Morris, and Pratt [llJ as a FORTRAN subroutine. Although a FORTRAN subroutine can use a variable-length array if it receives the array and its length as arguments, every local storage location must be allocated when the subroutine is compiled; i. e., there is no provision for "dynamic storage allocation" during execution. On the other hand, the Knuth-Morris-Pratt algoriuses a number of local storage locations which grows with the size of the input to the algorithm. This rules out any straightforward implementation without limiting the generality (at least in principle) of the algorithm, and it motivates our search for fast string-matching algorithms which use less space. The string-matching problem is to find all instances of a "pattern" string x as a subword (contiguous SUbstring) in a "text" string y. The classical "naive" algorithm (trying the pattern from scratch starting at each successive text position) requires time proportional to Ixl·lyl in the worst case, while the "fast" algorithm of Knuth, Morris, and Pratt requires time proportional to only Ixl + Iy I in the worst case. (We use Iwl to denote the length of the character string w.) On the other hand, the naive algorithm requires only a fixed number of additional memory locations, while the fast algorithm requires about Ixl additional memory locations in every case. It is the latter fact that makes a general straightforward implementation of the fast algorithm impossible without dynamic storage allocation. (A "straightforward implementation" would neither change the contents of the Ixl + Iy I memory locations initially containing x and y nor store more than O(log(lxl + Iyl)) bits (enough for an arbitrary reference into x or y) in a single memory location.) Our results improve each algorithm in its weak suit. (1) We show how to reduce the additional space utilization by the fast algorithm down to O(log Ixl) memory locations. Although theoretically this does still require some dynamic storage allocation in the worst case, a mere one hundred additional memory locations suffice to accommodate every pattern up to a billion characters long and many non-worst-case patterns longer than that. As in the Knuth-Morris-Pratt algorithm, the text need not be "backed up" or stored at all. (E. g., it could be read sequentially from a card reader.) Alternatively, if the text is buffered, the algorithm can read the text and detect the pattern instances in real time. (Galil observed that the Knuth-MorrisPratt algorithm has this property [7J.) On a multitape Turing machine with three input heads, this linear-time algorithm can be implemented in space O((log Ixl)2) • (2) We show how to reduce the running time of the naive algori thm all the way down to O( Ixl E( Ixl + Iy I)) for any fixed E> O. Thus we get an almost linear-time algorithm which can be implemented without any dynamic storage allocation at all. Like the slow naive algorithm, this faster algorithm can be implemented as a FORTRAN subroutine which receives the text only sequentially, say from a card reader. Also like the naive algorithm, in fact, this faster one can be implemented on a two-way multihead finite automaton with only a single, one-way head available for reading the text. (The number of heads used to read the pattern is proportional to liE.) On a single-tape Turing machine with three input heads, this almost linear-time algorithm can be implemented in space O(log Ixl) • The algorithms referred to in paragraphs (1) and (2) above are the extremes in a time-space-trade-off hiPart of this work was done while the first author w~s visiting the Computer Sciences Department at the IBM Thomas J •. Watson Resea:ch Center. The work was supported ~n part by the Bat-Sheva Fund (first author) and the National Sc~ence Foundat~on under grant MCS77-06613 (second author).