The Prefix Function and the KMP Algorithm

Definitions of String Prefixes and Suffixes

For definitions of prefixes, proper prefixes, suffixes, and proper suffixes of strings, see String Basics.

Prefix Function

Definition

Given a string of length nn, denoted ss, its prefix function is defined as an array of length nn denoted π\pi. The definition of π[i]\pi[i] is:

  1. If the substring s[0…i]s[0\dots i] has a pair of equal proper prefix and proper suffix, namely s[0…k−1]s[0\dots k-1] and s[i−(k−1)…i]s[i - (k - 1) \dots i], then π[i]\pi[i] is the length of this equal proper prefix (or proper suffix, since they are equal), that is, π[i]=k\pi[i]=k;
  2. If there is more than one such pair, π[i]\pi[i] is the length of the longest pair;
  3. If no equal pair exists, π[i]=0\pi[i]=0.

In simple terms, π[i]\pi[i] is the length of the longest equal proper prefix and proper suffix of the substring s[0…i]s[0\dots i].

In mathematical notation:

π[i]=max⁡k=0…i{k:s[0…k−1]=s[i−(k−1)…i]}\pi[i] = \max_{k = 0 \dots i}\{k: s[0 \dots k - 1] = s[i - (k - 1) \dots i]\}

In particular, define π[0]=0\pi[0]=0.

Process

For example, consider the string abcabcd.

π[0]=0\pi[0]=0, because a has no proper prefix or proper suffix, so it is 0 by definition.

π[1]=0\pi[1]=0, because ab has no equal proper prefix and proper suffix.

π[2]=0\pi[2]=0, because abc has no equal proper prefix and proper suffix.

π[3]=1\pi[3]=1, because abca has exactly one pair of equal proper prefix and proper suffix: a, of length 1.

π[4]=2\pi[4]=2, because the only equal proper prefix and proper suffix of abcab is ab, of length 2.

π[5]=3\pi[5]=3, because the only equal proper prefix and proper suffix of abcabc is abc, of length 3.

π[6]=0\pi[6]=0, because abcabcd has no equal proper prefix and proper suffix.

Similarly, the prefix function of aabaaab is [0,1,0,1,2,2,3][0, 1, 0, 1, 2, 2, 3].

Naive Algorithm for Computing the Prefix Function

Process

An algorithm that computes the prefix function directly from its definition proceeds as follows:

  • In a loop, for i=1→n−1i = 1\to n - 1, compute the prefix-function values π[i]\pi[i] in order, with π[0]\pi[0] assigned 00.
  • To compute the current prefix-function value π[i]\pi[i], try the variable jj from the largest possible proper-prefix length ii downward.
  • If the proper prefix and proper suffix of the current length are equal, then this length is π[i]\pi[i]; otherwise, decrease j by 1 and continue matching until j=0j=0.
  • If j=0j = 0 and no match has been found, set π[i]=0\pi[i] = 0 and move to the next index i+1i + 1.
Implementation

The implementation is as follows:

C++

// Note:
// string substr (size_t pos = 0, size_t len = npos) const;
vector<int> prefix_function(string s) {
  int n = (int)s.length();
  vector<int> pi(n);
  for (int i = 1; i < n; i++)
    for (int j = i; j >= 0; j--)
      if (s.substr(0, j) == s.substr(i - j + 1, j)) {
        pi[i] = j;
        break;
      }
  return pi;
}

Python

def prefix_function(s):
    n = len(s)
    pi = [0] * n
    for i in range(1, n):
        for j in range(i, -1, -1):
            if s[0:j] == s[i - j + 1 : i + 1]:
                pi[i] = j
                break
    return pi

Java

static int[] prefix_function(String s) {
    int n = s.length();
    int[] pi = new int[n];
    for (int i = 1; i < n; i++) {
        for (int j = i; j >= 0; j--) {
            if (s.substring(0, j).equals(s.substring(i - j + 1, i + 1))) {
                pi[i] = j;
                break;
            }
        }
    }
    return pi;
}

The time complexity of this algorithm is O(n3)O(n^3), leaving considerable room for improvement.

Efficient Algorithms for Computing the Prefix Function

First Optimization

The first important observation is that adjacent prefix-function values increase by at most 11.

As shown below, when taking the largest possible π[i+1]\pi[i+1], the newly added character s[i+1]s[i+1] must match the corresponding character, namely s[i+1]=s[π[i]]s[i+1]=s[\pi[i]]; in this case, π[i+1]=π[i]+1\pi[i+1] = \pi[i]+1.

s0 s1 s2⏞π[i]=3 s3⏟π[i+1]=4 … si−2 si−1 si⏞π[i]=3 si+1⏟π[i+1]=4\underbrace{\overbrace{s_0 ~ s_1 ~ s_2}^{\pi[i] = 3} ~ s_3}_{\pi[i+1] = 4} ~ \dots ~ \underbrace{\overbrace{s_{i-2} ~ s_{i-1} ~ s_{i}}^{\pi[i] = 3} ~ s_{i+1}}_{\pi[i+1] = 4}

Therefore, when moving to the next position, the prefix-function value either increases by one, remains unchanged, or decreases.

Implementation

The improved algorithm at this point is:

C++

vector<int> prefix_function(string s) {
  int n = (int)s.length();
  vector<int> pi(n);
  for (int i = 1; i < n; i++)
    for (int j = pi[i - 1] + 1; j >= 0; j--)  // improved: j=i => j=pi[i-1]+1
      if (s.substr(0, j) == s.substr(i - j + 1, j)) {
        pi[i] = j;
        break;
      }
  return pi;
}

Python

def prefix_function(s):
    n = len(s)
    pi = [0] * n
    for i in range(1, n):
        for j in range(pi[i - 1] + 1, -1, -1):
            if s[0:j] == s[i - j + 1 : i + 1]:
                pi[i] = j
                break
    return pi

Java

static int[] prefix_function(String s) {
    int n = s.length();
    int[] pi = new int[n];
    for (int i = 1; i < n; i++) {
        for (int j = pi[i - 1] + 1; j >= 0; j--) {
            if (s.substring(0, j).equals(s.substring(i - j + 1, i + 1))) {
                pi[i] = j;
                break;
            }
        }
    }
    return pi;
}

In this first improved algorithm, when computing each π[i]\pi[i], the best case is that the first string comparison produces a match, so the baseline number of string comparisons is n−1n-1.

Because j = pi[i-1]+1 (pi[0]=0) limits the maximum number of string comparisons, we can see that only the best case adds 11 to the upper bound on the number of comparisons; every comparison beyond the first consumes room for growth in later iterations.

Thus the case with the maximum number of comparisons has at least 11 comparison consumed and at most n−2n-2 comparisons accumulated, giving n−1+n−2=2n−3n-1 + n-2 = 2n-3 comparisons.

Therefore, after this optimization, computing the prefix function requires only O(n)O(n) string comparisons, and the total complexity decreases to O(n2)O(n^2).

Second Optimization

The first optimization considered the best case when computing π[i+1]\pi[i+1]: s[i+1]=s[π[i]]s[i+1]=s[\pi[i]], giving π[i+1]=π[i]+1\pi[i+1] = \pi[i]+1. Let us go further and consider how to jump when s[i+1]≠s[π[i]]s[i+1] \neq s[\pi[i]].

Figure: prefix str 1

As shown above, when a mismatch occurs, we want to find, for the substring s[0…i]s[0\dots i], the second-largest length after π[i]\pi[i], namely jj, while preserving the prefix property at position ii, namely s[0…j−1]=s[i−j+1…i]s[0 \dots j - 1] = s[i - j + 1 \dots i]:

s0 s1⏟j s2 s3⏞π[i] … si−3 si−2 si−1 si⏟j⏞π[i] si+1\overbrace{\underbrace{s_0 ~ s_1}_j ~ s_2 ~ s_3}^{\pi[i]} ~ \dots ~ \overbrace{s_{i-3} ~ s_{i-2} ~ \underbrace{s_{i-1} ~ s_{i}}_j}^{\pi[i]} ~ s_{i+1}

If such a length jj is found, only s[i+1]s[i + 1] and s[j]s[j] need to be compared again. If they are equal, then π[i+1]=j+1\pi[i + 1] = j + 1. Otherwise, for the substring s[0…i]s[0\dots i], find the second-largest length after jj, namely j(2)j^{(2)}, while preserving the prefix property, and repeat until j=0j = 0. If s[i+1]≠s[0]s[i + 1] \neq s[0], then π[i+1]=0\pi[i + 1] = 0. The second comparison is illustrated below.

Figure: prefix str 2

From the figure, because s[0…π[i]−1]=s[i−π[i]+1…i]s[0\dots \pi[i]-1] = s[i-\pi[i]+1\dots i], for s[0…i]s[0\dots i] the second-largest length is jj, with the property

s[0…j−1]=s[i−j+1…i]=s[π[i]−j…π[i]−1]s[0 \dots j - 1] = s[i - j + 1 \dots i]= s[\pi[i]-j\dots \pi[i]-1]

The diagram for this formula is shown below:

Figure: prefix str 3

In other words, jj is equivalent to the prefix-function value of the substring s[π[i]−1]s[\pi[i]-1], corresponding to the lower half of the figure above, namely j=π[π[i]−1]j=\pi[\pi[i]-1]. Similarly, the second-largest length after jj is equivalent to the prefix-function value of s[j−1]s[j-1], j(2)=π[j−1]j^{(2)}=\pi[j-1].

Clearly, we obtain the following state transition for jj: j(n)=π[j(n−1)−1],  (j(n−1)>0)j^{(n)}=\pi[j^{(n-1)}-1], \ \ (j^{(n-1)}>0).

Final Algorithm

Thus we can construct an algorithm that performs no string comparisons and only O(n)O(n) operations.

Moreover, its implementation is surprisingly short and intuitive:

Implementation

C++

vector<int> prefix_function(string s) {
  int n = (int)s.length();
  vector<int> pi(n);
  for (int i = 1; i < n; i++) {
    int j = pi[i - 1];
    while (j > 0 && s[i] != s[j]) j = pi[j - 1];
    if (s[i] == s[j]) j++;
    pi[i] = j;
  }
  return pi;
}

Python

def prefix_function(s):
    n = len(s)
    pi = [0] * n
    for i in range(1, n):
        j = pi[i - 1]
        while j > 0 and s[i] != s[j]:
            j = pi[j - 1]
        if s[i] == s[j]:
            j += 1
        pi[i] = j
    return pi

Java

static int[] prefix_function(String s) {
    int n = s.length();
    int[] pi = new int[n];
    for (int i = 1; i < n; i++) {
        int j = pi[i - 1];
        while (j > 0 && s.charAt(i) != s.charAt(j)) {
            j = pi[j - 1];
        }
        if (s.charAt(i) == s.charAt(j)) {
            j++;
        }
        pi[i] = j;
    }
    return pi;
}

This is an online algorithm: it processes data as it arrives. For example, the string can be read one character at a time and the prefix-function value of each character can be computed immediately. The algorithm still needs to store the string itself and the previously computed prefix-function values, but if the maximum possible prefix-function value of the string is known in advance to be MM, then it is enough to store the first M+1M + 1 characters and their corresponding prefix-function values.

Applications

Finding a Substring in a String: The Knuth–Morris–Pratt Algorithm

This algorithm was jointly published by Knuth, Pratt, and Morris in 19771. This task is a typical application of the prefix function.

Process

Given a text tt and a string ss, find and report all occurrences of ss in tt.

For convenience, let nn denote the length of ss, and mm the length of the text tt.

Construct the string s+#+ts + \# + t, where #\# is a delimiter that appears in neither ss nor tt, and compute its prefix function. Now consider the meaning of the remaining prefix-function values after the first n+1n + 1 values, which belong to ss and the delimiter. By definition, π[i]\pi[i] is the length of the longest proper substring whose right endpoint is ii and which is also a prefix; in this setting, it is the length of the longest substring matching a prefix of ss and ending at ii. Because the delimiter exists, this length cannot exceed nn. If π[i]=n\pi[i] = n, then ss occurs in full at this position, that is, its right endpoint is at position ii. Note that this index is with respect to the string s+#+ts + \# + t.

Therefore, at a position ii, if π[i]=n\pi[i] = n, then the string ss occurs in tt at position i−(n−1)−(n+1)=i−2ni - (n - 1) - (n + 1) = i - 2n. The diagram below illustrates the indices.

Figure: strstr kmp indices

As mentioned when computing the prefix function, if we know that the prefix-function value never exceeds a fixed value, we do not need to store the entire string and prefix function; only their initial parts are needed. In this case, we only need to store s+#s + \# and the corresponding prefix-function values. We can read one character of tt at a time and compute the prefix-function value at the current position.

Thus the Knuth–Morris–Pratt algorithm (KMP) solves the problem in O(n+m)O(n + m) time and O(n)O(n) memory.

Implementation

C++

vector<int> find_occurrences(string text, string pattern) {
  string cur = pattern + '#' + text;
  int sz1 = text.size(), sz2 = pattern.size();
  vector<int> v;
  vector<int> lps = prefix_function(cur);
  for (int i = sz2 + 1; i <= sz1 + sz2; i++) {
    if (lps[i] == sz2) v.push_back(i - 2 * sz2);
  }
  return v;
}

Python

def find_occurrences(t, s):
    cur = s + "#" + t
    sz1, sz2 = len(t), len(s)
    ret = []
    lps = prefix_function(cur)
    for i in range(sz2 + 1, sz1 + sz2 + 1):
        if lps[i] == sz2:
            ret.append(i - 2 * sz2)
    return ret

Java

static List<Integer> find_occurrences(String text, String pattern) {
    String cur = pattern + '#' + text;
    int sz1 = text.length(), sz2 = pattern.length();
    List<Integer> v = new ArrayList<>();
    int[] lps = prefix_function(cur);
    for (int i = sz2 + 1; i <= sz1 + sz2; i++) {
        if (lps[i] == sz2) {
            v.add(i - 2 * sz2);
        }
    }
    return v;
}

Periods of a String

For a string ss and 0<p≤∣s∣0 < p \le |s|, if s[i]=s[i+p]s[i] = s[i+p] holds for every i∈[0,∣s∣−p−1]i \in [0, |s| - p - 1], then pp is called a period of ss.

For a string ss and 0≤r<∣s∣0 \le r < |s|, if the prefix of ss of length rr equals the suffix of length rr, then the prefix of ss of length rr is called a border of ss.

The existence in ss of a border of length rr implies that ∣s∣−r|s|-r is a period of ss.

By the definition of the prefix function, all border lengths of ss are π[n−1],π[π[n−1]−1],…\pi[n-1],\pi[\pi[n-1]-1], \ldots.2

Therefore, in O(n)O(n) time, all periods of ss can be found. Since π[n−1]\pi[n-1] is the length of the longest border of ss, n−π[n−1]n - \pi[n-1] is the shortest period of ss.

Counting the Occurrences of Each Prefix

We discuss two problems. Given a string of length nn, denoted ss, the first variant asks for the number of occurrences of each prefix s[0…i]s[0 \dots i] in the same string, while the second asks for the number of occurrences of each prefix s[0…i]s[0 \dots i] in another given string tt.

First consider the first problem. At position ii, consider the prefix-function value π[i]\pi[i]. By definition, it means that a prefix of ss of length π[i]\pi[i] occurs at position ii and ends at ii, and that no longer prefix satisfies this definition. At the same time, shorter prefixes may also end at this position. We encounter a question already answered when computing the prefix function: given a prefix of length jj that is also a suffix ending at ii, what is the next smaller prefix length k<jk < j that is also a suffix ending at ii? Therefore, with right endpoint ii, there is a prefix of length π[i]\pi[i], a prefix of length π[π[i]−1]\pi[\pi[i] - 1], a prefix of length π[π[π[i]−1]−1]\pi[\pi[\pi[i] - 1] - 1], and so on, until the length becomes 00. Thus we can compute the answer as follows.

Implementation

C++

vector<int> ans(n + 1);
for (int i = 0; i < n; i++) ans[pi[i]]++;
for (int i = n - 1; i > 0; i--) ans[pi[i - 1]] += ans[i];
for (int i = 0; i <= n; i++) ans[i]++;

Python

ans = [0] * (n + 1)
for i in range(0, n):
    ans[pi[i]] += 1
for i in range(n - 1, 0, -1):
    ans[pi[i - 1]] += ans[i]
for i in range(0, n + 1):
    ans[i] += 1

Explanation

In the code above, first count how many times each prefix-function value appears in the array π\pi, then compute the final answer: if a prefix of length ii occurs exactly ans[i]\text{ans}[i] times, this value must be added to the occurrence count of its longest substring that is both a suffix and a prefix. Finally, to count the original prefix, add 11 to each result.

Now consider the second problem. Apply the technique from Knuth–Morris–Pratt: construct s+#+ts + \# + t and compute its prefix function. The only difference from the first problem is that we care only about prefix-function values related to tt, namely those with i≥n+1i \ge n + 1, namely π[i]\pi[i]. Once these values are known, apply the same algorithm as in the first problem.

Number of Distinct Substrings in a String

Given a string of length nn, denoted ss, we want to compute the number of distinct substrings.

We solve this problem iteratively. In other words, given the current number of distinct substrings, we need a way to recompute it after appending one character to the end of ss.

Let kk be the current number of distinct substrings of ss. Append a new character cc to ss. Clearly, some new substrings ending in cc will appear. We want to count those ending in this character that have not appeared before.

Construct t=s+ct = s + c and reverse it to obtain t∼t^{\sim}. The task is now to determine how many prefixes of t∼t^{\sim} do not occur anywhere else in t∼t^{\sim}. If the maximum prefix-function value of t∼t^{\sim} is πmax⁡\pi_{\max}, then the longest prefix that occurs in ss has length πmax⁡\pi_{\max}. Naturally, every shorter prefix also occurs.

Therefore, the number of new substrings after adding a new character is ∣s∣+1−πmax⁡|s| + 1 - \pi_{\max}.

Thus, for each appended character, the number of new substrings can be computed in O(n)O(n) time, giving total complexity O(n2)O(n^2).

It is worth noting that we can also recompute the number of distinct substrings when adding a character at the beginning, or removing a character from the end or beginning.

String Compression

Given a string of length nn, denoted ss, find its shortest “compressed” representation: the shortest string tt such that ss can be represented by concatenating one or more copies of tt.

Clearly, we only need to find the length of tt. Once the length is known, the answer is the prefix of ss of that length.

Compute the prefix function of ss. Using its final value π[n−1]\pi[n - 1], define k=n−π[n−1]k = n - \pi[n - 1]. We will prove that if kk divides nn, then kk is the answer; otherwise no valid compression exists, and the answer is nn.

Assume that nn is divisible by kk. Then the string can be divided into blocks of length kk. By the definition of the prefix function, its prefix of length n−kn - k equals its suffix. This means that the last block equals the second-to-last block, the second-to-last equals the third-to-last, and so on. Consequently, all blocks are equal, so ss can be compressed to length kk.

Proof

We still need to prove that this value is optimal. Indeed, if there were a compressed representation shorter than kk, then the final prefix-function value π[n−1]\pi[n - 1] would necessarily be greater than n−kn - k. Thus kk is the answer.

Now suppose that nn is not divisible by kk. We prove by contradiction that the answer is nn3. Assume that the shortest compressed representation rr has length pp (pp divides nn), so ss is divided into n/p≥2n / p \ge 2 blocks. Then the final prefix-function value π[n−1]\pi[n - 1] must be greater than n−pn - p (otherwise nn would be divisible by kk), so the suffix it represents partially covers the first block. Now consider the second block of the string. It has two descriptions: the first is r0r1…rp−1r_0 r_1 \dots r_{p - 1}, and the second is rp−krp−k+1…rp−1r0r1…rp−k−1r_{p - k} r_{p - k + 1} \dots r_{p - 1} r_0 r_1 \dots r_{p - k - 1}. Since both descriptions represent the same string, we obtain a system of pp equations, which can be written as r(i+k) mod p=ri mod pr_{(i + k) \bmod p} = r_{i \bmod p}, where ⋅ mod p\cdot \bmod p denotes the least nonnegative remainder modulo pp.

r0 r1 r2 r3 r4 r5⏞p r0 r1 r2 r3 r4r5⏞pr0 r1 r2 r3 r0 r1 r2 r3 r4 r5⏞p r0 r1⏟π[11]=8\begin{gathered} \overbrace{r_0 ~ r_1 ~ r_2 ~ r_3 ~ r_4 ~ r_5}^p ~ \overbrace{r_0 ~ r_1 ~ r_2 ~ r_3 ~ r_4 r_5}^p \\ r_0 ~ r_1 ~ r_2 ~ r_3 ~ \underbrace{\overbrace{r_0 ~ r_1 ~ r_2 ~ r_3 ~ r_4 ~ r_5}^p ~ r_0 ~ r_1}_{\pi[11] = 8} \end{gathered}

By the extended Euclidean algorithm, we can obtain xx and yy satisfying xk+yp=gcd⁡(k,p)xk + yp = \gcd(k, p). By appropriately adding multiples of the equation pk−kp=0pk - kp = 0, we can obtain x′>0x' > 0 and y′<0y' < 0 satisfying x′k+y′p=gcd⁡(k,p)x'k + y'p = \gcd(k, p). This means that by repeatedly applying equations from the system above, we obtain the new system r(i+gcd⁡(k,p)) mod p=ri mod pr_{(i + \gcd(k, p)) \bmod p} = r_{i \bmod p}.

Since gcd⁡(k,p)\gcd(k, p) divides pp, this means that gcd⁡(k,p)\gcd(k, p) is a period of rr. Moreover, because π[n−1]>n−p\pi[n - 1] > n - p, we have n−π[n−1]=k<pn - \pi[n - 1] = k < p, so gcd⁡(k,p)\gcd(k, p) is a period smaller than pp of rr. Therefore, ss has a compressed representation of length gcd⁡(k,p)<p\gcd(k, p) < p, contradicting the minimality of pp.

In conclusion, no compressed representation of length less than kk exists, so the answer is kk.

Constructing an Automaton from the Prefix Function

Let us return to the new string formed by concatenating two strings with a delimiter. For strings ss and tt, compute the prefix function of s+#+ts + \# + t. Clearly, because #\# is a delimiter, the prefix-function value never exceeds ∣s∣|s|. Therefore, we only need to store s+#s + \# and its corresponding prefix-function values, after which we can compute the prefix-function values for all subsequent characters dynamically:

s0 s1 … sn−1 #⏟need to store t0 t1 … tm−1⏟do not need to store\underbrace{s_0 ~ s_1 ~ \dots ~ s_{n-1} ~ \#}_{\text{need to store}} ~ \underbrace{t_0 ~ t_1 ~ \dots ~ t_{m-1}}_{\text{do not need to store}}

In fact, knowing the next character of string tt, namely cc, and the prefix-function value at the previous position is enough to compute the next prefix-function value; no other character or prefix-function value of tt is needed.

In other words, we can construct an automaton (a finite-state machine): its state is the current prefix-function value, and the transition from one state to another is determined by the next character.

Thus, even without the string tt, we can construct a transition table ( old π,c)→ new −π( \text { old } \pi , c ) \rightarrow \text { new } _ { - } \pi using the construction algorithm:

Implementation
void compute_automaton(string s, vector<vector<int>>& aut) {
  s += '#';
  int n = s.size();
  vector<int> pi = prefix_function(s);
  aut.assign(n, vector<int>(26));
  for (int i = 0; i < n; i++) {
    for (int c = 0; c < 26; c++) {
      int j = i;
      while (j > 0 && 'a' + c != s[j]) j = pi[j - 1];
      if ('a' + c == s[j]) j++;
      aut[i][c] = j;
    }
  }
}

In this form, for a lowercase alphabet, the time complexity is O(∣Σ∣n2)O(|\Sigma|n^2). We can apply dynamic programming to reuse the parts already computed in the table. Whenever we move from jj to π[j−1]\pi[j - 1], we are saying that the state reached by transition (j,c)(j, c) is the same as that reached by transition (π[j−1],c)(\pi[j - 1], c), and the latter answer has already been computed exactly.

Implementation
void compute_automaton(string s, vector<vector<int>>& aut) {
  s += '#';
  int n = s.size();
  vector<int> pi = prefix_function(s);
  aut.assign(n, vector<int>(26));
  for (int i = 0; i < n; i++) {
    for (int c = 0; c < 26; c++) {
      if (i > 0 && 'a' + c != s[i])
        aut[i][c] = aut[pi[i - 1]][c];
      else
        aut[i][c] = i + ('a' + c == s[i]);
    }
  }
}

The automaton can ultimately be constructed in O(∣Σ∣n)O(|\Sigma|n) time.

When is this automaton useful? First, recall that most of the time we use the prefix function of s+#+ts + \# + t for one purpose: finding all occurrences of ss in tt.

Therefore, the most direct benefit of this automaton is accelerating the computation of the prefix function of s+#+ts + \# + t.

By constructing the automaton for s+#s + \#, we no longer need to store ss or its corresponding prefix-function values. All transitions have already been computed in the table.

There is also a second, less direct application. When tt is a huge string constructed by rules, the automaton can accelerate the computation. Gray strings and strings recursively constructed from short input strings are examples.

For completeness, consider the following problem: given a number k≤105k \le 10^5 and a string of length ≤105\le 10^5, denoted ss, compute the number of occurrences of ss in the kk-th Gray string. Recall that Gray strings are defined as follows:

g1=ag2=abag3=abacabag4=abacabadabacaba\begin{aligned} g_1 &= \mathtt{a}\\ g_2 &= \mathtt{aba}\\ g_3 &= \mathtt{abacaba}\\ g_4 &= \mathtt{abacabadabacaba} \end{aligned}

Because of its astronomical length, even constructing tt is impossible here: the kk-th Gray string has 2k−12^k - 1 characters. However, its final prefix-function value can be computed efficiently if only a few prefix-function values at the beginning are known.

In addition to the automaton, compute G[i][j]G[i][j], the automaton state after starting from state jj and processing gig_i, and K[i][j]K[i][j], after starting from state jj and processing gig_i, the number of occurrences of ss in gig_i. In fact, K[i][j]K[i][j] is the number of times the prefix function takes the value ∣s∣|s| during the operation. The answer is K[k][0]K[k][0].

How should these values be computed? By definition, the initial conditions are G[0][j]=jG[0][j] = j and K[0][j]=0K[0][j] = 0. All subsequent values can be obtained from earlier values and the automaton. To compute the values for a particular ii, recall that gig_i is the concatenation of gi−1g_{i - 1}, the ii-th character of the alphabet, and gi−1g_{i - 1}. Thus the automaton passes through the following states:

mid=aut[G[i−1][j]][i]G[i][j]=G[i−1][mid]\begin{gathered} \text{mid} = \text{aut}[G[i - 1][j]][i] \\ G[i][j] = G[i - 1][\text{mid}] \end{gathered}

The value of K[i][j]K[i][j] can be computed similarly.

K[i][j]=K[i−1][j]+[mid==∣s∣]+K[i−1][mid]K[i][j] = K[i - 1][j] + [\text{mid} == |s|] + K[i - 1][\text{mid}]

Here, [⋅][\cdot] is 11 when its expression is true and 00 otherwise. In summary, this solves the Gray-string problem and a large class of similar problems. For example, the same method solves the following problem: given a string ss and a collection of patterns tit_i, each pattern consists of ordinary characters and may recursively insert previously defined strings in the form tkcntt_{k}^{\text{cnt}}, meaning that tkt_k must be inserted cnt\text{cnt} times at that position. Here is an example of such patterns:

t1=abdecat2=abc+t130+abdt3=t250+t1100t4=t210+t3100\begin{aligned} t_1 &= \mathtt{abdeca} \\ t_2 &= \mathtt{abc} + t_1^{30} + \mathtt{abd} \\ t_3 &= t_2^{50} + t_1^{100} \\ t_4 &= t_2^{10} + t_3^{100} \end{aligned}

Recursive substitution makes the string lengths grow explosively; they can even reach an order of magnitude of 100100100^{100}. We need to find the number of occurrences of string ss in each string.

This problem can likewise be solved by constructing a prefix-function automaton. As before, use previously computed results to calculate the transitions for each pattern and then count the answers.

Exercises

References and Notes

This page was mainly translated from the article Префикс-функция. Алгоритм Кнута-Морриса-Пратта and its English translation Prefix function. Knuth–Morris–Pratt algorithm. The Russian version is licensed under Public Domain + Leave a Link; the English version is licensed under CC-BY-SA 4.0.

Footnotes

  1. Knuth, Donald E., James H. Morris, Jr, and Vaughan R. Pratt. "Fast pattern matching in strings." SIAM journal on computing 6.2 (1977): 323-350.doi: 10.1137/0206024 ↩

  2. Jin Ce - Selected Lectures on String Algorithms ↩

  3. The proof of this part in both the Russian and English versions appears to be incorrect. This part of the proof was added by the author of this article. ↩

This content comes from OI Wiki: The Prefix Function and the KMP Algorithm. See the original for authors and revision history. AIOJ has adapted the formatting, in-site links, and resource paths.

Content is shared under CC BY-SA 4.0 and SATA license; additional terms are governed by the original notice. Thanks to the OI Wiki authors and contributors; per the SATA terms, please visit the OI Wiki repository and star it to show your support.