KMP算法中的next数组构造是一个关键问题,但是博主发现next数组的构造方法有很多,不同的算法实现中构造出的next数组也有所不同,有些方法实在是晦涩难懂。【经典算法】——KMP,深入讲解next数组的求解,这篇博客提供了一种比较简明的理解和构造next数组的方法。引入最长部分匹配长度的概念,实质上就是字符串的前缀(去掉首字符)和后缀(去掉尾字符)相同的最大长度:
“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。
(我们称被匹配的串为源串string source, 作为要匹配对象的串为模式串string pattern)
next[i]实质上就是模式串中从串起始到第i个字符为止的最长部分匹配长度。当我们用next数组做匹配时如果有nof个字符匹配成功,即patter[nof]匹配失败时, 查看next[nof],表示此字符前有next[nof]个字符和模式串起始的next[nof]个字符是匹配的,故只需要把源串当前比较字符继续与pattern[next[nof]]比较即可。
构造next数组时,next[0]=0没有意义,next[0]=0必然,每次根据next[i]计算next[i+1],如果pattern[i]与pattern[next[i]]相等next[i+1]=next[i]+1,否则就像匹配时一样向前迭代查找。
void buildNext(string s, int next[])
{
int sLength = s.length();
next[0] = 0; next[1] = 0;
int nof;//number of fits
for(int i=1; i<sLength; i++)
{
nof = next[i];//当前匹配字符数,即是s[i]前有nof个字符的最长前缀
//下一个字符是否匹配
while(s[i]!=s[nof] && nof!=0)
nof = next[nof];
if(s[i]!=s[nof]) next[i+1] = 0;
else next[i+1] = nof+1;
}
}
这样实现的匹配算法比较简明, 设置nof使代码清晰有条理。外循环就表示源串待匹配字符指针的移动。循环内部实质上就执行三个操作:1.根据next数组设置nof。2.判断当前字符是否匹配(如果匹配nof当然要+1)3.判断匹配是否结束
//返回第一次匹配成功时在源串中的起始位置
int KMPMapping(string s, string p)
{
if(p.length()==0 || s.length()==0)
return -1;
int *next = new int[p.length()];
buildNext(p, next);
int i = 0, j = 0, nof = 0;
for(int i=0; i<s.length();i++)
{
//next[nof]表示匹配nof个字符之后p[nof](因为s从0开始)匹配失败时的最长前缀字符数,之s[i]要与p[next[nof]]比较
while(nof>0 && p[nof] != s[i])
nof = next[nof];
//表示当前有nof个字符匹配
if(p[nof] == s[i])
nof++;
if(nof == p.length())
return i - p.length() + 1;
}
return -1;
}