字符串匹配

BF(Brute Force)算法

  • BF是最符合人类直觉的字符串匹配算法,但是主串的下标i经常要往回走,无法利用已匹配信息,效率不够好
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    int bruteForce(const string& t, const string& p) {
    int i = 0, j = 0;
    // i和j都是非负数,所以可以和无符号直接比较
    while (i < t.size() && j < p.size()) {
    if (t[i] == p[j]) {
    ++i; ++j;
    } else {
    i = i - j + 1;
    j = 0;
    }
    }
    if (j == p.size())
    return i - j;
    else
    return -1;
    }

KMP算法

  • 思想:“利用已部分匹配的信息,保持i指针不回溯,通过修改j指针,让模式串尽量移动到有效的位置”
  • 定义next数组: next[j] = d 表示当 t[i] != p[j] 时,j下一次匹配的位置。注意到,下标从0开始,d值实际上是下标j前的最长前后缀子串长度
  • 求取next的过程本身就是p串与自己匹配的过程
    1. p[i]==p[j] ,则 p[++i] = ++j
    2. p[i]!=p[j] ,则利用前面已求得的next数组,j=next[j] ;直到无法找到,此时 j=-1 ,自动进入第一个if语句,此时i往后走一步,妙
      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
      vector<int> getNext(const string& p) {
      int n = p.size();
      vector<int> next(n);
      next[0] = -1;
      int i = 0, j = -1;
      // !!! i < n 则会在下面越界,因为0已经求过了,循环只会进行n-1次
      while (i < n - 1) {
      if (j == -1 || p[i] == p[j]) {
      ++i, ++j;
      next[i] = j;
      } else {
      j = next[j];
      }
      }
      return next;
      }

      int KMP(const string& t, const string& p) {
      vector<int> next = getNext(p);
      int n = t.size(), m = p.size();
      int i = 0, j = 0;
      // !!! j可以为负数,和无符号比较会转换为无符号比较法(南辕北辙)
      while (i < n && j < m) {
      if (j == -1 || t[i] == p[j]) {
      ++i; ++j;
      } else {
      j = next[j];
      }
      }
      if (j == p.size())
      return i - j;
      else
      return -1;
      }

KMP之Next优化

  • 比如[ A B A B ]这个串,按照上面所述KMP得到的结果是[-1, 0, 0, 1],然而,例如匹配的是[ A B A B D E],在下标为3处失败了,next指导去下标1处继续匹配,但是,下标1和下标3都是B,没有必要再进行比较了。诸如此类的例子还有[ A A A A B],用上述KMP得到的next[-1, 0, 1, 2, 3]
  • 方法就是在 ++i, ++j 后加一步判断,本来直接 next[i]=j 但是如果 p[i]==p[j] 则如果以后匹配 p[i] 失败,则匹配 p[j] 肯定也失败啊,所以如果两者相等,直接 next[i] = next[j] 。此时的结果对于[ A B A B ]产生的是[-1, 0, -1, 0];对于[ A A A A B]产生的是[-1, -1, -1, -1, 3]
  • 加三行代码就行了
    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
    vector<int> getNext(const string& p) {
    int n = p.size();
    vector<int> next(n);
    next[0] = -1;
    int i = 0, j = -1;
    while (i < n - 1) {
    if (j == -1 || p[i] == p[j]) {
    ++i, ++j;
    if (p[i] == p[j]) // new
    next[i] = next[j]; // new
    else // new
    next[i] = j;
    }
    else {
    j = next[j];
    }
    }
    return next;
    }

    int KMP(const string& t, const string& p) {
    vector<int> next = getNext(p);
    int n = t.size(), m = p.size();
    int i = 0, j = 0;
    while (i < n && j < m) {
    if (j == -1 || t[i] == p[j]) {
    ++i; ++j;
    }
    else {
    j = next[j];
    }
    }
    if (j == p.size())
    return i - j;
    else
    return -1;
    }

参考资料

  • 博客园博主:sofu6