朴素算法的思想最好理解当相同时往后匹配,不同时就再从第一个开始重新匹配,对于无规则乱序还可以,但是如果主串为“abacba”,模式串中如“abab”,我们可以发现当abab后一个匹配失败时朴素算法需要重新从主串的第二个“b”开始与匹配串重新比较,但是KMP就改进了这个缺点,我们已经知道了两个串前三个一样,那为什么不从主串的c与匹配串的第一个b比较,那主串的c就是主串匹配失败的位置,匹配串就是匹配失败时当前位置的前一个的真子串长度,因为匹配失败前都已经匹配成功,而只要知道前面的真子串长度。因为是从0开始记录所以就是真子串的长度而不用加一。
所以我们大概了解了KMP算法:如果当前字符匹配成功就两个都往后移,如果失败,主串位置不变,匹配串返回当前位置前(不含当前字符)真子串的长度。
失效函数也就是求返回当前位置前(不含当前字符)真子串的长度,那么也可以看成是同样使用KMP算法来求。我们令第1个f[0]的为-1
void GetFailure(const String &pat, int f[])
//求匹配串pat的失效函数值
{
f[0]=-1; // 初始f[0]的值为-1
int j = 0, k = -1;
while (j < pat.GetLength() - 1)
if (k == -1 || pat[k] == pat[j])
f[++j]=++k;// f[1]=0,如果相同就等于原子串长度+1
else // pat[k]与pat[j]不匹配
k = f[k]; // 寻求新的匹配字符
}
那么如果失效函数为求出的每位真子串长度为F[];
那么KMP算法的主体部分就是
int KMP_find(const String &ob, const String &pat, int p = 0)
// pat匹配串,ob主串,主串第p个位置开始匹配
{
int *f = new int[pat.GetLength()]; // 为失效函数值数组f分配空间
GetFailure(pat, f); // 通过失效函数求每位真子串
int i = p, j = 0;
while (i < ob.GetLength() && j < pat.GetLength() && pat.GetLength() - j <= ob.GetLength() - i)
//i记录主串当前匹配到的位置,j记录主串当前匹配到的位置,如果主串剩下的少于匹配串剩下的就结束
if (j == -1 || pat[j] == ob[i]) {
i++; j++; // 模式串pat与目标串ob的当前位置向后移
}
else // pat[j]与ob[i]不匹配
j = f[j]; // 寻找新的模式串pat的匹配字符位置
delete []f; // 释f所占用的存储空间
if (j < pat.GetLength())
return -1; // 匹配失败
else
return i - j; // 匹配成功
}
现在我们发现KMP算法貌似已经很不错了,但是假如有一个匹配串abcab,主串abcad…,如果直接使用KMP。我们会发现第二段匹配时要ab与ad比较,但是如果只看匹配串,因为前面和后面都是ab,所以没有意义再比较一遍这时候就要改进失效函数,多加一条判断,当前返回的pat[f[4]]== pat[4],如果相同让他们的f[]相同,这样就像是跳过了这段无意义的比较。
void GetFailure(const String &pat, int f[]){
f[0]=-1; // 初始f[0]的值为-1
int j=0,k = -1;
while (j < pat.GetLength() - 1)
if(k == -1 || pat[k] == pat[j]){
f[++j]=++k;
if (pat[k]==pat[j]) {
f[j]=f[k]; //f[]相同
}
else {
f[j]=k; //不变
}
}
else
k = f[k];
}