KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris 和 V.R.Pratt 同时发现,因此人们称它为克努特——莫里斯——普拉特操作(简称KMP算法)。KMP算法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是实现一个next()函数,函数本身包含了模式串的局部匹配信息。时间复杂度O(m+n)。
KMP算法可以称为是一个模式匹配算法,主要强大的地方在于,它可以省去很多时间,因为当它匹配失败以后,会跳回最可能匹配成功的地方,这时候,就要引入一个 next 数组了,而 next 在C++里面好像是一个关键词所以代码里面用 nt 代替。
next 数组的意义:
next 数组里面的变量,存的是最有可能匹配的长度,也就是在模式串中,前缀和后缀相等的最大长度。
以"ABCDABD"为例
- "A"的前缀和后缀都为空集,共有元素的长度为0;
- "AB"的前缀为[A],后缀为[B],共有元素的长度为0;
- "ABC"的前缀为[A, AB],后缀为[BC, C],共有元素的长度0;
- "ABCD"的前缀为[A, AB, ABC],后缀为[BCD, CD, D],共有元素的长度为0;
- "ABCDA"的前缀为[A, AB, ABC, ABCD],后缀为[BCDA, CDA, DA, A],共有元素为"A",长度为1;
- "ABCDAB"的前缀为[A, AB, ABC, ABCD, ABCDA],后缀为[BCDAB, CDAB, DAB, AB, B],共有元素为"AB",长度为2;
- "ABCDABD"的前缀为[A, AB, ABC, ABCD, ABCDA, ABCDAB],后缀为[BCDABD, CDABD, DABD, ABD, BD, D],共有元素的长度为0。
next 数组中,第一个值,也就是next [0] = -1,而且,next [n]里面存的是 str[0] ~ str[n - 1]的前缀和后缀相等的最大长度。
求 next 数组的方法:
void getnext() ///a为主串,b为副串
{
int j = 0, k = -1;
nt[0] = -1;
while(j < len2) ///len2代表的是模式串(副串)的长度
{
if(k == -1 || b[j] == b[k]) ///匹配成功的话,就匹配下一个字符
{
j++;
k++;
nt[j] = k;
}
else ///失败了的话,就跳回上一次成功的地方
k = nt[k];
}
}
求出 next 数组以后,基本就没啥难度了,
匹配算法(通用):
while(i < len1) ///i为在主串中活动的指针,j为在副串中活动的指针
{
if(j == len2) ///整个串匹配成功
{
flag = 1;
break;
}
if(a[i] == b[j] || j == -1) ///单个变量匹配成功,进行下一个变量
{ ///j = -1 的时候也就是第一个的时候,直接进行下一个即可
i++;
j++;
}
else ///单个变量匹配失败,跳回上一次匹配成功的地方
j = nt[j];
}
if(i == len1 && j == len2) ///查看一下最后匹配的时候有没有成功
{
flag = 1;
}
这样就完成啦!
上面就是KMP算法的详细编写过程了,其实拓展KMP的话呢,就是在副串特别长,而且有连续相同字符的时候,会比普通KMP快得多;具体操作来说呢,就是 getnext 不太一样,加一句话就行了:
void getnext()
{
int j = 0, k = -1;
nt[j] = k;
while(j < len2)
{
if(k == -1 || brr[j] == brr[k])
{
j++;
k++;
if(brr[j] != brr[k]) ///拓展KMP的精髓
nt[j] = k;
else
nt[j] = nt[k];
}
else
k = nt[k];
}
}
好了,这样就完了,下面是例题博客哦!
例题博客:
OVER!