算法与数据结构——字符串匹配——KMP

1. 普通的字符串匹配

有两个字符串如下
0 1 2 3 4 5 6 7 8 9
字符串t
d a b a c a b a b a c
字符串p
a b a b

通常可以这样写

char *
func(char *t, char *p)
{
   for (i = 0; i < strlen(t); i++) {
      for (j = 0; j < strlen(p); j++) {
          if (t[i + j] != p[j])
             break;
      }
      if (j == strlen(p))
         return t + i - strlen(p);
   }
   return NULL;
}

这个算法的时间复杂度是 O((m-n+1)*n)
这个算法的问题在于 这一步

   for (i = 0; i < strlen(t); i++) {
      for (j = 0; j < strlen(p); j++) {  // 问题
          if (t[i + j] != p[j])
             break;
      }
   }

当发生不匹配时,每次都从 0 开始重新匹配,
之所以如此,是因为 我们无法知道 t[i] 是 p[0] - p[strlen(p)]的哪个位置。
所以只能 把每个位置都尝试一次。

2. KMP算法

KMP 针对如上问题改进。
其关键是能确定 t[i] 在 p[0] - p[strlen(p)] 的位置,
如此就有

char *
kmp(char *t, char *p)
{
   for (i = 0, j = 0; i < strlen(t) && j < strlen(p) && j != -1; i++) {
        if (t[i] != p[j])
           j = next[j];
        else
           j++;
   }

   if (j == strlen(p))
      return t + i - strlen(p);
   return NULL;
}

KMP 的时间复杂度为 O(m+n)
其关键是,当发生不匹配时,预先知道 t[i] 相对于 p 串的位置,为 next[j]

        if (t[i] != p[j])
           j = next[j];

2.1 next数组

由于已知 p[n] - p[m] 和 t[i] - t[j] 已经匹配,
在 p[m+1] 处发生不匹配,
则 希望知道 p[m+1] 相对于 t[k]。
实际上比较的是,下一个可能匹配的子串 的前缀 和 已匹配 子串 后缀的。
而只有当一个串的前缀,后缀 相同时,k>0,否则k=0,
例如,串 abab 可以这么求
a : 前缀没有,后缀没有,相同部分 0
ab : 前缀a, 后缀b, 相同部分长度 0
aba : 前缀a, 后缀a, 相同部分长度 1
abab : 前缀ab,后缀ab,相同部分长度 2

next数组是除掉当前字符的最大 相同前后缀长度,所以对上面数值右移一位,第一个元素设为-1.
所以能构建next数组

void getnext(char *p, char *next)
{
        next[0] = -1;
        int i = 0, j  = -1;

        while (i < strlen(p)) {
                if (j == -1 || p[i] == p[j]) {
                        i++;
                        j++;
                        next[i] = j;
                }
                else
                        j = next[j];
        }
}

由于模式串是提前知道的,所以对模式串求 next数组,

posted on 2022-05-10 11:26  开心种树  阅读(39)  评论(0编辑  收藏  举报