今天整理一下关于KMP算法的一些理解,如果有什么不对的地方还请大家指正!
它的作用我简单的介绍一下:
关于KMP算法,我们首先要明白几个概念:
① 字符串前缀、最长字符串前缀
②字符串后缀、最长字符串后缀
③公共字符串
现在解释一下:假设有一个字符串:abababaa
字符串前缀:除了最后一个字符(不包括\0)以外的所有字符的集合,
例如:
"a"、"ab"、"aba"、"abab"、"ababa"、"ababab"、"abababa"
最长字符串前缀:"abababa"
字符串后缀:除了第一个字符(不包括\0)以外的所有字符的集合,
例如:
"a"、"aa"、"baa"、"abaa"、"babaa"、"ababaa"、"bababaa"
最长字符串后缀:"bababaa"
公共字符串:指该字符串的前缀与后缀中相交的那部分字符;(注意:这里的相交字符串的首字符必须和前缀首字符相同,末尾字符必须和后缀的末尾字符相同)
好多书上说KMP有一个next数组,那next数组有什么作用呢?
其实,next数组记录的是字符串的最长公共字符串字符的个数。
举个例子:还是以上述的字符串为例:abababaa
next[0]是求字符串"a"的最长公共字符串""(空)的个数,可以看出,个数为0;
next[1]是求字符串"ab"的最长公共字符串""的个数,可以看出,个数为0;
next[2]是求字符串"aba"的最长公共字符串"a"(空)的个数,可以看出,个数为1;
next[3]是求字符串"abab"的最长公共字符串""的"ab"个数,可以看出,个数为2;
next[4]是求字符串"ababa"的最长公共字符串"aba"的个数,可以看出,个数为3;
next[5]是求字符串"ababab"的最长公共字符串"abab"的个数,可以看出,个数为4;
next[6]是求字符串"abababa"的最长公共字子符串"ababa"的个数,可以看出,个数为5;
next[7]是求字符串"abababaa"的最长公共字符串"a"的个数,可以看出,个数为1;
那么,该如何求字符串的next数组?也就是k值,我们称其为对称程度
这里存在几种场景:(设next[0]的k值为0,表示没有公共字符串)
第一种:匹配过程中不存在中断
当前字符的前一个字符的k值为0时,将当前字符与子串的第一个字符比较,如果前面都是0,说明前面没有匹配的公共字符,则如果要求有公共子串,则最多要求其与第一个字符相同。
举个例子:
假设有abcdea这样的字符串;b和a比较,不相同,则b的k值为0;接着 c和a比较,不相同,则c的k值为0;紧接着,d和e的k值都为0,最后a的k值为多少就要看它和第一个字符是否相同,相同,则在前一个字符即e的k值的基础上累加1;
( 在不存在中断的情况下)不仅仅前面一个字符的k值为0,还可以为1、2、3等,假如当前字符的前一个字符的k值为1,说明,前面的字符有1个公共子串,那当前字符就要与第二个字符相比较,为什么呢?因为,如果前面的字符k值为1,那就说明,它只会与第一个字符匹配,同样的,如果前面的字符k值为2,说明前面的字符与第二个字符匹配,那当前字符就要与第三各字符匹配,看是否相同;
举个例子:
假设有ababab这样的字符串,第二个字符b先与首字符a比较,不同,next[1]=0;则第三位a与首字符a比较,相同,则next[2]=1(前面0的基础上加1);那么第四位b就要与第二位b比较,又相等,在累加1即next[3]=2;接着,五位a与第三位的a比较,就这样,如果一直匹配成功,k就一直累加;
当然,我们不可能就这样一直匹配下去,可能会有一个字符不匹配;
这就是第二种场景,在连续比较相同过程中出现了不匹配的情况:
例如,字符串abababaa,它的next[0~6]也就是前七个字符的k值分别为0、0、1、2、3、4、5都是连续匹配的,当最后一个字符a与第六个字符b不匹配时,注意了,这也是关键所在,当发现有一个字符不匹配时,并不是立刻要该字符与首字符比较,注意这点。原因在于,在当前的匹配程度(k)值为5的情况下不匹配,那就再从匹配程度相较5小的匹配程度中去找,画个图可能大家就会清楚:
存在这样一种场景:看最后一位与第四位看是否匹配
如果还不匹配,还有种情况:
总结一下:在连续匹配成功的情况下,出现了匹配失败的情况,那么,就要看失配位前面的字符的k值是多少,让失配的那位与第k+1的字符进行比较;
例如:字符串abababaa,最后一位与第六位不匹配,我们就看第五位的k值是多少,这个例子中,第五位k值为3,那么,最后一位又与第(3+1)位也就是第四位匹配,还不匹配,那就看第三位的k值,为1,继续让最后一位与(1+1)位也就是第二位比较,如此循环,直到最后一位与第一位进行比较,即可求出最后一位k值;
贴上代码:我们继续看看
#include<iostream>
#include<string>
using namespace std;
void Get_next(char *str,int *next,size_t n)
{
next[0] = 0;
int k = 0;
for (size_t i = 1; i < n; ++i)
{
while ((k>0) && (str[k] != str[i]))
{
k = next[k-1];//回溯
}
if (str[k] == str[i])
{
k += 1;
}
next[i] = k;
}
}
int Get_KMP(char* str, char* ptr, size_t slen, size_t plen)
{
int *next = new int[plen];
int k = 0;
Get_next(ptr,next,plen);
for (size_t i = 0; i < slen; i++)
{
while (k>0 && ptr[k] != str[i])
{
k = next[k-1];
}
if (ptr[k] == str[i])
{
k += 1;
}
if (k == plen)
{
return i - plen+1;//返回第一个匹配的下标位置
}
}
return -1;
}
int main()
{
char *str = "fgabcdabcdabaa";
char *ptr = "abcdabaa";
int a = Get_KMP(str,ptr,14,8);
cout << a << endl;
system("pause");
return 0;
}
我再一次解释一下上述get_kmp所做的事:
k = next[k-1];//回溯
这句代码所做的事就是前面我们所说连续匹配时出现不匹配就要回溯,他所表达的意思就是:
看失配位前面的字符的k值是多少,让失配的那位与第k+1的字符进行比较;
看到这里,大家可以结合我所提供的例子,结合代码,好好体会一下,如果我所表述的有问题,欢迎大家指正,后面我会进行修改,补充。
未完待续。。。。。。。