目录

字符串匹配

Pattern Matching

字符串匹配问题又称模式匹配(pattern matching)。该问题可以概括为「给定字符串 S 和 T,在主串 S 中寻找子串 T 」。字符 T 称为模式串 (pattern)。

暴力做法 BF

简称 BF (Brute Force) 算法。该算法的基本思想是从主串 T 的第一个字符开始和模式串 T 的第一个字符进行比较,若相等,则继续比较二者的后续字符;否则,模式串 T 回退到第一个字符,重新和主串 S 的第二个字符进行比较。如此往复,直到 S 或 T 中所有字符比较完毕。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
  void bruteForce(String s, String p) {
    int sLen = s.length();
    int pLen = p.length();

    for (int i = 0; i < sLen - pLen; i++) {
      boolean flag = true;
      for (int j = 0; j < pLen; j++) {
        if (s.indexOf(i + j) != p.indexOf(j)) {
          flag = false;
          break;
        }
      }

      if (flag) {
        System.out.println(i);
      }
    }
  }

设 n 为主串的长度,m 为模式串的长度。默认 m<=n。

  • 最好的情况:匹配成功时,时间复杂度为 $O(n)$ ;匹配失败时,时间复杂度为 $O(m)$。
  • 最坏的情况:每趟不成功的匹配都发生在模式串的最后一个字符,BF 算法要执行 m(n-m+1) 次比较,时间复杂度为 $O(mn)$。

KMP 算法

Knuth-Morris-Pratt 算法是一个著名的字符串匹配算法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class KMP1 {
  private int[][] dp;
  private String pat;

  public KMP1(String pat) {
    this.pat = pat;
    int M = pat.length();
    // dp[状态][字符] = 下个状态
    dp = new int[M][256];
    // base case
    dp[0][pat.charAt(0)] = 1;
    // 影子状态 X 初始为 0
    int X = 0;
    // 构建状态转移图(稍改的更紧凑了)
    for (int j = 1; j < M; j++) {
      for (int c = 0; c < 256; c++) {
        dp[j][c] = dp[X][c];
        dp[j][pat.charAt(j)] = j + 1;
        // 更新影子状态
        X = dp[X][pat.charAt(j)];
      }
    }
  }

  public int search(String txt) {
    int M = pat.length();
    int N = txt.length();
    // pat 的初始态为 0
    int j = 0;
    for (int i = 0; i < N; i++) {
      // 计算 pat 的下一个状态
      j = dp[j][txt.charAt(i)];
      // 到达终止态,返回结果
      if (j == M) {
        return i - M + 1;
      }
    }
    // 没到达终止态,匹配失败
    return -1;
  }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class KMP {

  /** 在文本 text 中寻找模式串 pattern,返回所有匹配的位置开头 */
  public List<Integer> search(String text, String pattern) {
    List<Integer> positions = new ArrayList<>();
    int[] maxMatchLengths = calculateMaxMatchLengths(pattern);
    int count = 0;
    for (int i = 0; i < text.length(); i++) {
      while (count > 0 && pattern.charAt(count) != text.charAt(i)) {
        count = maxMatchLengths[count - 1];
      }
      if (pattern.charAt(count) == text.charAt(i)) {
        count++;
      }
      if (count == pattern.length()) {
        positions.add(i - pattern.length() + 1);
        count = maxMatchLengths[count - 1];
      }
    }
    return positions;
  }

  /** 构造模式串 pattern 的最大匹配数表 Partial Match Table */
  private int[] calculateMaxMatchLengths(String pattern) {
    int[] maxMatchLengths = new int[pattern.length()];
    int maxLength = 0;
    for (int i = 1; i < pattern.length(); i++) {
      while (maxLength > 0 && pattern.charAt(maxLength) != pattern.charAt(i)) {
        maxLength = maxMatchLengths[maxLength - 1];
      }
      if (pattern.charAt(maxLength) == pattern.charAt(i)) {
        maxLength++;
      }
      maxMatchLengths[i] = maxLength;
    }
    return maxMatchLengths;
  }
}

用 $O(m+n)$ 的时间以及 O(n) 的内存解决了该问题。

BoyerMoore

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
public class BoyerMoore {

  public static void main(String[] args) {
    String text = "here is a simple example";
    String pattern = "example";
    BoyerMoore bm = new BoyerMoore();
    bm.boyerMoore(pattern, text);
  }

  public void boyerMoore(String pattern, String text) {
    int m = pattern.length();
    int n = text.length();
    Map<String, Integer> bmBc = new HashMap<>();
    int[] bmGs = new int[m];
    // proprocessing
    preBmBc(pattern, m, bmBc);
    preBmGs(pattern, m, bmGs);
    // searching
    int j = 0;
    int i = 0;
    int count = 0;
    while (j <= n - m) {
      for (i = m - 1; i >= 0 && pattern.charAt(i) == text.charAt(i + j); i--) { // 用于计数
        count++;
      }
      if (i < 0) {
        System.out.println("one position is:" + j);
        j += bmGs[0];
      } else {
        j += Math.max(bmGs[i], getBmBc(String.valueOf(text.charAt(i + j)), bmBc, m) - m + 1 + i);
      }
    }
    System.out.println("count:" + count);
  }

  private void preBmBc(String pattern, int patLength, Map<String, Integer> bmBc) {
    System.out.println("bmbc start process...");
    {
      for (int i = patLength - 2; i >= 0; i--)
        if (!bmBc.containsKey(String.valueOf(pattern.charAt(i)))) {
          bmBc.put(String.valueOf(pattern.charAt(i)), (Integer) (patLength - i - 1));
        }
    }
  }

  private void preBmGs(String pattern, int patLength, int[] bmGs) {
    int i, j;
    int[] suffix = new int[patLength];
    suffix(pattern, patLength, suffix);
    // 模式串中没有子串匹配上好后缀,也找不到一个最大前缀
    for (i = 0; i < patLength; i++) {
      bmGs[i] = patLength;
    }
    // 模式串中没有子串匹配上好后缀,但找到一个最大前缀
    j = 0;
    for (i = patLength - 1; i >= 0; i--) {
      if (suffix[i] == i + 1) {
        for (; j < patLength - 1 - i; j++) {
          if (bmGs[j] == patLength) {
            bmGs[j] = patLength - 1 - i;
          }
        }
      }
    }
    // 模式串中有子串匹配上好后缀
    for (i = 0; i < patLength - 1; i++) {
      bmGs[patLength - 1 - suffix[i]] = patLength - 1 - i;
    }
    System.out.print("bmGs:");
    for (i = 0; i < patLength; i++) {
      System.out.print(bmGs[i] + ",");
    }
    System.out.println();
  }

  private void suffix(String pattern, int patLength, int[] suffix) {
    suffix[patLength - 1] = patLength;
    int q = 0;
    for (int i = patLength - 2; i >= 0; i--) {
      q = i;
      while (q >= 0 && pattern.charAt(q) == pattern.charAt(patLength - 1 - i + q)) {
        q--;
      }
      suffix[i] = i - q;
    }
  }

  private int getBmBc(String c, Map<String, Integer> bmBc, int m) {
    // 如果在规则中则返回相应的值,否则返回pattern的长度
    if (bmBc.containsKey(c)) {
      return bmBc.get(c);
    } else {
      return m;
    }
  }
}