KMP算法
假设现在我们面临这样一个问题:给两个字符串s、p,请问p是否是s的子串?这就需要定位子串,也既串匹配。串匹配运算的应用非常广泛,在搜索引擎、拼写检查、语言翻译、数据压缩等应用中,都需要进行串匹配。KMP算法就是串的一种匹配算法。
KMP算法可以在O(n+m)的时间数量级上完成串的模式匹配。相比其他的串的匹配算法,KMP的优势在于:每当一趟匹配过程中出现字符比较不等时,不需回溯指针,而是利用已经得到的“部分匹配”的结果将模式向右“滑动”尽可能远的一段距离后,继续行匹配。
KMP算法的匹配流程
“假设现在文本串S匹配到 i 位置,模式串P匹配到 j 位置 如果j = -1,或者当前字符匹配成功(即S[i] ==
P[j]),都令i++,j++,继续匹配下一个字符; 如果j != -1,且当前字符匹配失败(即S[i] != P[j]),则令 i
不变,j = next[j]。此举意味着失配时,模式串P相对于文本串S向右移动了j - next [j] 位。
换言之,当匹配失败时,模式串向右移动的位数为:失配字符所在位置 - 失配字符对应的next 值,即移动的实际位数为:j -
next[j],且此值大于等于1。”
现在我们用一个问题介绍一下。假设主串为“s1s2…sn”,模式串为“t1t2…tm”,为实现KMP算法,需要解决下述问题:当匹配过程中产生“失佩”(si≠tj)时,模式串“向右滑动”的可行的距离多远?
假设此时应与模式中第k(k<j)个字符比较,则模式中前k-1个字符的子串必须满足下列关系式,且不可能存在k’>k满足下列关系式①
“t1t2…tk-1”=“si-k+1si-k+2…si-1”
而已经得到的“部分区配”的结果是②
“tj-k+1tj-k+2…tj-1”=“si-k+1si-k+2…si-1”
由①②推得下列等式③
“t1t2…tk-1”=“tj-k+1tj-k+2…tj-1”
反之,若模式串中存在满足③的两个子串,则当匹配过程中,主串中第i个字符与模式串第j个字符比较不等时,仅需将模式向右滑动至模式中第k个字符和主串中第i个字符对齐,此时,模式中头k-1个字符的字串“t1t2…tk-1”必定与主串中第i个字符之前长度为k-1的子串“si-k+1si-k+2…si-1”相等,由此,匹配仅需从模式中第k个字符与主串中第i个字符开始,依次向后进行比较。
若令next[j]=k,则next[j]表明当模式中第j个字符与主串中相应字符“失配”时,在模式中需重新和主串中该字符进行比较的字符的位置。由此可引出模式串中next函数的定义:
0 j=1(t1与si比较不等时,下一步进行t1与si+1的比较)
next[j]= Max{k l1<k<j且有“t1t2…tk-1”=“tj-k+1tj-k+2…tj-1”}
1 k=1(不存在相同子串,下一步进行t1与si的比较)
算法描述
int Index_KMP(SString S,SString T,int pos)
{//利用模式串T的next函数求T在主串中第pos个字符之后的位置
//其中,T非空,1<=pos<=S.length
i=pos;j=1;
while(i<=S.length && j<=T.length) //两个串均未比较到串尾
{
if(j==0||S.ch[i]==T.ch[i]) {++i;++j;} // 继续比较后继字符
else j=next[j]; //模式串向右移动
}
if(j>T.length) return i-T.length; //匹配成功
else return 0; //匹配失败
}
KMP算法是在已知模式串的next函数值的基础上执行的,那么,如何求得模式串中next函数值?
1.j的前面的k个字符与开头的k个字符进行比较,若对应相等且T[k]==T[j]但T[k]!=T[0],则next[j]=0; j的前面的k个字符与开头的k个字符进行比较,若对应相等且T[k]==T[j]但T[k]==T[0],则next[j]=-1; j的前面的k个字符与开头的k个字符进行比较,若对应相等且T[k]!=T[j],则next[j]=k;
2.j的前面的k个字符与开头的k个字符进行比较,若不等且T[k]==T[0],则next[j]=-1;j的前面的k个字符与开头的k个字符进行比较,若不等且T[k]!=T[0],则next[j]=0。
算法描述
void get_next(SString T, int next[ ] )
{//求模式串T的next函数值并存入数组next
i=1;next [1]=0;j=0;
while(i<T.length)
{
if(j==0|| T.ch[i]==T.ch[j] ){++i;++j;next[i]=j; }
eise j=next[j];
}
}
上面定义的next函数在某些情况下有缺陷。例如模式“aaaab”在和主串“aaabaaaab”匹配时,当i=4、j=4时s.ch[4]≠t.ch[4],由next[j]的指示还需进行i=4、j=3,i=4、j=2,i=4、j=1这三次比较。实际上,因为模式中的1~3个字符和第4个字符都相等,因此不需要再和主串中第4个字符相比较,哦,可以在模式中浆膜是连续向右滑动4个字符的位置直接进行i=5、j=1时字符比较。也即,若按上述定义得到next[j]=k,而模式中tj=tk,则当主串中字符si和tj比较不等时,不需与tk进行比较,直接与Tnext[k]比较,换而言之,此时next[j]=next[k]。由此得到next函数修正值。
算法描述
void get_nextval(SString T, int nextval[ ])
{//求模式串T的next函数修正值并存入数组nextval
i=1;nextval[1]=0;j=0;
while(i<T.length)
{
if(j==0||T.ch[i]==T.ch[j])
{
++i;++j;
if(T.ch[i] !=T.ch[j]) nextval [i]=j;
else nextval [i]=nextval [j];
}
}
}