BF算法到匹配不成功的时候子串需要回溯,这样就会提高时间复杂度,KMP算法是每当一趟匹配过程中出现字符比较不相等时,不要回溯i指针,而是利用已经得到的“部分匹配”的结果将模式向右“滑动”尽可能远的一段距离后,继续进行比较。
以一般情况进行说明,假设主串为"S1, S2, ...,Sn",模式串为:"T1, T2, ...Tm",当主串中的第i个字符与模式中的第j个字符比较不相等时,主串的第i个字符应该与模式中的哪个字符再比较?
假设此时应该与模式串的第k(k<j)个字符继续比较,则模式中前k-1个字符的子串必须满足下面这个关系式,且不可能存在k1>k满足该关系式 (因为k<j,若k1>k,则K1字符在j之后,j还没完成无法进行下一个)
满足公式:"T1T2...Tk-1 = Si-k+1Si-k...Si-1"(Si-k+1Si-k...Si-1表示主串的i字符前有k-1个字符的子串)
因为模式串是从1可以和主串匹配到j-1,所有下列该公式:
Tj-k+1Tj-k+2...Tj-1 = Si-k+1Si-k...Si-1(Tj-k+1Tj-k+2...Tj-1是模式串第i个元素前k-1个元素,因为模式串是在第j个元素和主串的第i匹配不成功,则,前面的元素是匹配成功的)
故可以推得:"T1T2...Tk-1 = Tj-k+1Tj-k+2...Tj-1"
因此当主串的第i个元素与模式串的第j个元素匹配失败时,就可以将模式向右滑动至模式串中的第k个元素与主串中的第i个元素对齐就成。
因此令next[j] = k(类似于一个函数),则next[j]表示当模式串中第j个字符与主串的第i个字符不匹配时,在模式中需要和主串该字符进行比较的字符的位置。可以定义一个next的函数的定义
K = next[j] =
- 0 j = 1(T1与Si比较不等时,下一步进行T1与Si+1比较)
- Max (K| 1<k<j,且有"T1T2...Tk-1 = Tj-k+1Tj-k+2...Tj-1",K = k-1 + 1)
- 1 k = 1 (不存在相同的子串,下一步进行T1与Si比较)
若在匹配过程中Si = Ti匹配成功,则i和j分别增加1
若未匹配成功,j = next[j],往回退有两种可能:
- 退到某个j时匹配成功,T(next[next[..next[j]..]] == Si,则指针都加1,再继续进行比较
- j退到值为0,则继续将模式串往右继续滑动一个位置(就相当于匹配不成功的时候也应将模式串next[j]移到和主串的第i个字符对齐,若最后一个还是不匹配,故还需要将其右滑动一个位置然后使模式串的第1个元素与主串的第i+1个元素对齐)
int Index_KMP(SString S, SString T, int pos)
{
int i,j,pos;//pos是用来记录模式串是在主串的哪一个位置开始完全匹配成功,
i = 1;
j = 1;
while(i < S.length && j < T.length)//两个串均未到串尾
{
if(j == 0 || S.ch[i] == T.ch[j])//规定j == 0是因为next[1] = 0;这时就是未匹配成功的第2个可
能;S.ch[i] == T.ch[j]匹配成功,都要加1;
{
++i;//主串向右移动
++j;//模式串向右移动
}
else
j = next[j];
}
if(j > T.length)
return pos = i - T.length;//匹配成功,返回pos值
else
return 0;//匹配失败,因为在规定的时候串的第一不放有效值,故返回0的时候为匹配失败
}
这是在知道next[]的值,那么next[]应该如何求?
首先next[1] = 0;
设next[j] = k,则满足:"T1T2...Tk-1 = Tj-k+1Tj-k+2...Tj-1",那么next[j+1] = ?
(1)若Tk = Tj,则表明模式串中:"T1T2...Tk-1Tk = Tj-k+1Tj-k+2...Tj-1Tj",所以next[j+1] = k+1;
(2)若 Tk != Tj,则继续比较Tnext[k]与Tj,知道匹配成功,那此时next[j+1] = next[此时该字符与T匹配成功的位置]或者到不存在模式串的字符与Tj匹配成功,即next[j] = next[1]+1 = 1
void get_next(SString T, int next())
{
int i = 1;
next[1] = 0;
int j = 0;
while(i<T.length)
{
if(j == 0 || T.ch[i] == T.ch[j])
{
++i;
++j;
next[i] = j;
else
j = next[j];
}
}
}
//因为j == 0;所以i= 2; j = 1; 则next[2] = 1;然后该求next[3],由前面的可知若if()中满足条件则next[3] = next[2]+1,这就相当于一种递归
前面说的都是从大往下来说明,而该代码看起来是从小往大,但是当要求next[3]的时候,仍然是以next[2]来运算,如果比较之后不相等,然后执行j = next[j],和前面说如何求next[j+1]的(2)是相同的,如果if条件达成,就会++j,就和前面(2)中一样next[j] = next[1]+1 = 1
求nextval[];
//事先求出next[]
nextval[1] = 0;
for(int j = 2; j < T.length; ++j)
{
if(T.ch[j] == T.ch[next[j]])
{
nextval[j] = nextval[next[j]];
}
else
nextval[j] = next[j];
}
求next[]快速方法:
例题:
串"ababaaababaa"的next数组为(C)
A,012345678999 B,012121111212 C,011234223456 D,0123012322345
第一个肯定是0;第二个仅有一个前面仅有一个字符,所以为1;第三个前面的前缀为a,后缀为b,所以next[3] = 0+1 = 1;... 第六个相等的最长的前后缀为:aba,所以next[6] = 3+1 = 4