void PrefixMatch(const string DstStr, vector<int> & NextRes){
NextRes[0] = 0;
int k = 0;
for (int i = 1; i < DstStr.size(); i++)
{
while (k > 0 && DstStr[k] != DstStr[i])
k = NextRes[k - 1];
if (DstStr[k] == DstStr[i])
k++;
NextRes[i] = k;
}
}
KMP算法用于在长字符串中找目标字符串。
最简单的匹配算法当然是拿目标字符串从长字符串开头开始一个一个匹配,如果不能全部匹配,就把目标字符串向后移动一位,继续匹配,如下图。
KMP的思想就是我不想每次匹配不成功了,一个位置一个位置挪了,我想每次能不能多挪几个,那么,如果大胆一点,我可以直接挪上一步已经比较过的字符个数个位置,在这里就是6个位置,如下图
但是我们也都知道,这样子肯定有时候会漏掉一些情况。
那么我们假设存在这样被漏掉的情况,在图中,其实我们可以发现,其实在第5位也可以发现与目标字符串匹配的,如下图。
那么,问题就变成了,怎么判断在已经比较过的字符串中,是否有可以从头开始比较的位置呢?假设已经比较了k个字符,这里有几点已知条件:
已经比较过的字符串是和目标字符串前几位完全相同的一段,即目标字符串前k位;
如果在已经比较过的字符串即目标字符串前k位字符中存在可以继续从此处开始的位置,那么从这个位置开始到这k个字符串最后一个这段字符串都应该与目标字符串前n个匹配;
这样的话,那我们如果事先知道:
设目标字符串str_obj,长度为m, 给出任意值k<=m,求出值最小的值i,使得str_obj(i), str_obj(i+1)……str_obj(k)=str_obj(1),str_obj(2)……str_obj(k-i+1)。这样的话,我就知道每次匹配不成功,我只需要向前移动k-i位。
在这个例子里,第一次匹配了6位,第七位不同,那么k=6,发现1-6的字符中发现最后两位与目标字符串前两位相同,这说明i=2,那么我们本次可以移动的步数应该是4,也就是如图:
所以只要我们在开始匹配前,把目标字符串每一位开始向前的连续字符串可以和目标字符串前几位完全匹配的最大数得到,那么在匹配的过程中就可以直接查询得到本次可以前移的步数了,这里有一点需要注意,这句话中的前者,向前的连续字符串,不能包括第一个,如果包括第一个,那永远都会有从第一个到这一位和目标字符串前k位完全相同,也就是说,有字符串1-m,对于 k=1,2,3…m,在字符2-k中找最小的i使得字符i- -k=字符1- -(k-i+1)。而这个过程就是被称为前缀函数的过程。这个例子里,各个位置的重复度或匹配值如下,可以自己揣摩一下。
而计算这个数值的过程也很费解,先看程序
代码如下:
void PrefixMatch(const string DstStr, vector<int> & NextRes){
NextRes[0] = 0;
int k = 0;
for (int i = 1; i < DstStr.size(); i++)
{
while (k > 0 && DstStr[k] != DstStr[i])
k = NextRes[k - 1];
if (DstStr[k] == DstStr[i])
k++;
NextRes[i] = k;
}
}
比较容易理解的是for循环里的if语句:有了上一步的匹配结果,下一步的话只要新增的字符还能匹配上那么这一步能匹配上的就是上一步的结果加1;
比较难理解的是while语句:先不管k>0,当新增字符不匹配的时候,那么把k置为上一步匹配到的字符串的长度的位置,这个的位置很关键。如果新增字符无法匹配,我们不把这一步的结果置为0的原因是,再之前匹配到的字符串中可能还存在能匹配到的子字符串,这样的话,我们就需要
(1)看已经匹配到的字符串中最长的匹配长度;
(2)然后再比较之后一个字符和新增字符是否相等。
这样的话(1)其实是我们之前的结果,就是语句
k = NextRes[k - 1];
(2)就是判断条件
DstStr[k] != DstStr[i]
如果不满足就迭代上述过程,有点递归的意思,那么退出条件就是,什么时候已经没有子字符串了,就可以退出了,这就是k>0.这就是全部。
具体的KMP从原理上和上边的求next数组相当相似,仔细想一下其实,(1)求next数组是求自身和自身的匹配问题,而(2)CMP是求目标字符串和长字符串的匹配问题。 问题(1)要求出目标字符串所有从头开始的子字符串的最大匹配长度,问题(2)只要目标字符串和长字符串匹配的最大长度等于目标字符串的部分。这样想就很简单了,直接上代码吧
int main()
{
string LongStr, DstStr;
cin >> LongStr >> DstStr;
int* NextRes = new int[DstStr.size()];
PrefixMatch(DstStr, NextRes);
int q = 0;
for (int i = 0; i < LongStr.size(); i++)
{
while (q > 0 && DstStr[q] != LongStr[i])
q = NextRes[q - 1];
if (LongStr[i] == DstStr[q])
q++;
if (q == DstStr.size()){
cout << "match from " << i - DstStr.size() + 1 << " to " << i << endl;
q = NextRes[q - 1];
}
}
return 0;
}