最近看了些KMP算法的讲解,但由于个人理解能力有限,所以感觉不是很好理解,最后在弄懂之后,决定把它按自己的理解记录下来,希望能对想要学习的朋友有所帮助
此处套用百科上对KMP算法的解释:
KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是通过一个next()函数实现,函数本身包含了模式串的局部匹配信息。KMP算法的时间复杂度O(m+n)
根据上文所述,很明显,该算法的核心是利用已经匹配过的数据,避免后面无谓的冗余匹配,它的关键要点就是Next数组
Next数组大约就是下面这种东西:
模式串 | A | B | A | B | C | A |
---|---|---|---|---|---|---|
下标 | 0 | 1 | 2 | 3 | 4 | 5 |
Next值 | 0 | 0 | 1 | 2 | 0 | 1 |
Next值按个人理解就是指模式串中重合的公共前后缀中最大的重合长度,也就是最大公共前后缀
首先,这里需要说明一下前后缀的含义:
前缀:不包含最后一个字符,以首字符开头的连续子串为前缀
后缀:不包含第一个字符,到最后一个字符结束的连续子串为后缀
那么模式串ABABCA是如何求出对应的Next值呢,我们可以将其分解进行计算
前缀 | 后缀 |
A | A |
AB | CA |
ABA | BCA |
ABAB | ABCA |
ABABC | BABCA |
知道了前后缀,计算两者重合的长度就很简单了,我们对模式串进行推导一下:
A
只有一个字符,无法对比,将其置空为0
AB
前缀A,后缀B
没有重合串,将其置空为0
ABA
前缀A,AB,后缀A,BA
重合串为A,长度为1
ABAB
前缀A,AB,ABA,后缀B,AB,BAB
重合串为AB,长度为2,将其置为2
ABABC
前缀A,AB,ABA,ABAB,后缀C,BC,ABC,BABC
均无法与后缀带C的后缀重合,将其置为0
ABABCA 前缀A,AB,ABA,ABAB,ABABC,后缀A,CA,BCA,ABCA,BABCA
重合串为A,将其置为1
最终我们得出了和上面一样的Next数组
模式串 | A | B | A | B | C | A |
---|---|---|---|---|---|---|
下标 | 0 | 1 | 2 | 3 | 4 | 5 |
Next值 | 0 | 0 | 1 | 2 | 0 | 1 |
或许看完上面你会疑惑这个最大公共前后缀,最大是什么意思,上面没有体现出来,我们以AABAA为例:
AABAA
前缀A,AA,AAB,AABA,后缀A,AA,BAA,ABAA
其中前后缀A,AA均重合,这个时候,AA即为最大的前后缀,长度为2
Next数组的计算逻辑我们已经知道了,那么该如何转化为代码呢,这里才是最重要的关键点,好吧,这里想不出很好的文字层面的解释,建议看视频辅助,更加的形象,这里直接贴代码好了
/// <summary>
/// 计算模式串的Next(Prefix)数组值
/// </summary>
/// <param name="pattern">模式串</param>
/// <returns></returns>
public static int[] Prefix_Table(string pattern)
{
//根据Pattern模式串,计算获取相对应的Next(或Prefix)数组
int len = 0, i = 1;
int[] prefix = new int[pattern.Length];
prefix[len] = 0;//首元素只有一个无法计算前后缀匹配情况,直接赋值0
while (i < pattern.Length)
{
if (pattern[len] == pattern[i])//如果匹配成功
{
len++; //前缀串长度+1
prefix[i] = len; //赋值当前位置匹配最大前后缀长度
i++; //后缀索引加1
}
else//
{
if (len > 0)//如果之前匹配成功过,那么回溯到上一次的匹配长度进行匹配
{
len = prefix[len - 1];//前缀指针位置回溯上一次匹配位置
}
else//前面没有匹配成功过或当前匹配回溯无任何匹配成功状况
{
prefix[i] = len;
i++;
}
}
}
return prefix;
}