这篇文章不解释什么是O(n)记法
KMP
kmp是一个两串字符串比较的算法,分别为P:模式串,S:文本串。
我的文章一般都是废话少说,直接就进入主题。
我们假设现在需要匹配如下:
传统的做法:逐一的遍历 S 串,再遍历 P 串, S[ i ] != P[ j ] (i=0,j=0),因为不相等,所以 i 会自增 1 (++),j 回溯 0,当 i = 7 发生匹配,j++(j = 1), i = 8 发生匹配 j++(j = 2), i = 9 失配 j 回溯 0
这边 P 串只有三个字符,还好,但是如果 P 串的字符量很大,这开销很大,因为我们要回溯 P 串到位置 0 然后开始新的匹配
然而KMP算法可以根据 P 串提前计算好预知的回溯位置,减少我们的回溯位,减少开销。也就是网上 所谓的 NEXT 数组
(这里我们不讨论什么时间复杂度,抛开这些理论,站在程序这一面来理解)
我们先看看NEXT 数组是怎么计算的。假设我们现在的 P 不变。
然后定义一个 int[] 数组,数组的长度的 P 串的长度 + 1(因为我们占用下 0 下标,并且数组的 0 号位标识为 -1 ,1号位标识为 0 ,因为我们的回溯字符 P 最高就是 0)。
先上下代码,然后继续讲解。
_next = new int[p.Length + 1];
_next[0] = -1;
_next[1] = 0;
int i = 0;
int j = 1;
while (j < p.Length)
{
if (p[i] == p[j] || i == 0)
{
j++;
i++;
if (j < p.Length && p[i] != p[j])
{
_next[j] = i;
}
else
{
_next[j] = _next[i];
}
}
else
{
i = _next[i];
}
}
这里的 i 表示的是前缀字符,j 表示的是后缀字符。
什么是前缀,什么是后缀?
1,2,3,4,5,6 => 3 前缀 1 后缀2
=> 5 (前缀1 后缀4)and (前缀2,后缀3)
类似高斯先生的算法 从1 加到 100
我们可以手动演算一遍,方便牢记
这里的计算我并没有 计算 if (j < t.Length && t[i] != t[j]) 这个判断,这里是个小优化。
因为 假设我们有一串字符 S: SSSSSSSSSaaaCsssssdcacaaaaaGsscsssssssdac P:sssssdac
如果我们要进行匹配,我们可以大概看出,在匹配从C/G开始的时候就,会有开始的回溯,而且是一个一个的回溯,这样看起来不好,因为前面的 P 串的 sss..是相同的,我们可以考虑next数组保持一样的,这样我们就能直接回溯到指定的位置,从而减少回溯的次数。也就是 if (j < t.Length && t[i] != t[j]) 这个判断里面的语句了。
这里就是Next的数组计算了。
下篇再写后续的字符匹配