KMP算法
问题:
在字符串匹配问题中,给出两个字符串,文本串test,模式串pattern,如何判断模式串是否是文本串的一个子串
KMP算法核心:
- 模式串失配后,不是从下一个字符开始重新匹配,而是利用已有的信息,跳过不可能成功匹配的位置,减少匹配次数,达到快速匹配的目的;
- 失配之后具体要跳过多少位置,可以提前用模式串计算出来,计算出来的结果就存在next[]数组中;
- next[j] = k;
代表的是模式串的子串(0 ~ j-1)的前缀和后缀相同的最大长度;
其中前缀是指:除最后一个字符外,字符串的所有头部子串;
后缀是指:除第一个字符外,字符串的所有尾部子串;
因此 k < j 恒成立;
KMP算法过程:
设test串指针为 i, pattern串指针为 j:
此时部分匹配:
“test[i-j] ~ test[i-1]” == “pattern[0] ~ pattern[j-1]”,test[i] != pattern[j];且 next[j] == k;
由next数组定义知:
“pattern[0] ~ pattern[k-1]” == “pattern[j-k] ~ pattern[j-1]”
所以我们接下来只用匹配test[i] 与 pattern[k] 即可,避免了主串的回溯;
代码形式即为:j = next[j];
KMP主体代码:
const int MAXN = 10000;
int KMP(string test, string pattern)
{
int next[MAXN];
GetNext(next,pattern);
int i = 0, j = 0;
while(i < test.size() && j < pattern.size())
{
if(j == -1 || test[i] == pattern[j])//回溯到起点状态,从头匹配;或者当前字符匹配成功,匹配下一个字符
{
i ++;
j ++;
}
else
{
j = next[j];//当前字符串匹配失败,移动模式串指针
}
}
if(j == m) //匹配成功
{
return i-j+1;//返回第一个匹配串下标
}
else return -1;//匹配失败
}
next[]数组的求解:
由上面的主代码可知,现在我们面临的问题就是对next数组的求解;
具体求解过程:
next数组的求解具有很强的规律性以及递推关系;
在已知next[j]的情况下,求解next[j+1]:
- 设next[j]的值为k,说明在长为j的子串中,0 ~ k-1 的头部子串和 j-k ~ j-1 的尾部子串完全相同;
- 现在考虑长为 j+1的子串:
a. if ( patern[k] == pattern[j])
即pattern中第k+1个字符与第j+1个字符相同,那么显然next[j+1] = next[j]+1;
b. if (即patern[k] != pattrtn[j])
即pattern中第k+1个字符与第j+1个字符不同 ;
我们可以把这种情况看做一个pattern串与另一个pattern串匹配,出现了失配的现象,将 j 视为主串指针,k 视为目标串指针,则由KMP主体算法的演示过程可知,对k进行回溯而j不变;
即有: k = next[k] ;(next[k] 必定小于k)
Next数组求解代码
void GetNext(int next[], string pattern)//创建next表,本质上使用pattern自己与自己匹配来获得next数组
{
next[0] = -1;//初始化
int k = -1, j = 0;
while(j < pattern.size())//对每个子串求出其对应的next值
{
if(k == -1 || pattern[j] == pattern[i])
{
k ++;
j ++;
next[j] = k;
}
else
{
k = next[k];//next[i]是小于i的
}
}
}