子串的定位操作通常称作串的模式匹配,是各种串处理系统中最重要的操作之一。
算法有BF蛮力算法和KMP算法,KMP算法的特点是速度快。可以在O(n+m)的时间数量级上完成串的模式匹配操作。
算法思想:
与蛮力算法相比,其改进在于,每当一趟匹配过程中出现字符比较不等时,不需回溯i指针,而是利用已经得到的部分匹配的结果将模式向右滑动尽可能远的一段距离后,继续进行比较。
S.ch[i]与T.ch[j]失配时,只需找到S中截止到S[i-1]的真后缀,它与T中T.ch[i]开始的前缀相等且该子串尽量长,S.ch[i]接下来应该比较的字符记为T[k]。如何计算k呢?
S[i]与T[1]不配,则S[i]不应与T中元素比较,next[1]=0,i后移
若S[i]与 T[2]不配,只能再拿S[i]与T[1]比,故next[2]=1
假设j>=3 则找S中截止到S[i-1]的真后缀,它与T中T[1]开始的前缀相等并且子串尽量长,next函数值为子串长度+1
算法实现:
int Index_KMP(HString S,HString T,int pos){
//利用模式串T中额next函数求T在主串S中第pos个字符之后位置的
//KMP算法 。T非空 pos>=1&&pos<=S.length
if(pos<1||pos>S.length||!T.length) //pos值非法
return ERROR;
int i=pos,j=1,nextval[100];
Get_Nextval(T,nextval); //获得T中next值
while(i<=S.length&&j<=T.length){
if(j==0||S.ch[i-1]==T.ch[j-1]){ //j为0代表next[j]返回值0
//继续比较后续字符
i++;
j++;
}
else
j=nextval[j]; //模式串后移 i不变j后退
}
if(j>T.length)//匹配成功
return i-T.length;
return 0;
}
时间复杂度:
设主串的长度为n,模式串长度为m,在KMP算法中求next数组的时间复杂度为O(m),在后面的匹配中因主串S的下标不回溯,而且子串指针每后移一次则主串指针也后移,此时导致的移动总次数不会超过n+m,故复杂度O(n+m)。
递推法求next函数值:
初值next[1]=0;
假设next[i-1]的值为k,此时T[1..k-1]=T[i-k...i-2]。
若T[k]=T[i-1], next[i]=k+1。
若T[k]!=T[i-1],说明T[1..k]与T[i-k...i-1]只最后一个字符不匹配。如此应让T[i-1]与T[next[k]]比较,或说重置上次匹配时的next值k为next[k],即令k=next[k];
重复上述步骤,直到T[i-1]=T[k]或k=0,前者next[i]=k+1 后者next[i]=1.
void Get_Next(HString T, int next[]){
//求模式串T中的next值并存入数组next。
int i=1,j=0;
next[1]=0;
while(i<T.length){
if(j==0||T.ch[i-1]==T.ch[j-1]){ //如果T.ch[i-1]==T.ch[j-1] 表明真右字串与前面左字串相等
//获得next值 并比较后续字符
i++;
j++;
next[i]=j;
}
else //说明只最后字符不匹配 这时应该T.ch[i-1]==T.ch[next[j]-1] 比较 即j=next[j];
j=next[j];
}
}
改进的nextval函数:
void Get_Nextval(HString T, int nextval[]) {
//改进的求模式串T中的nextval值并存入数组nextval。
int i=1,j=0;
nextval[1]=0;
while(i<T.length){
if(j==0||T.ch[i-1]==T.ch[j-1]){
i++;
j++;
if(T.ch[i-1]!=T.ch[j-1])
nextval[i]=j;
else//如果字符与前一个相等 不需要比较 直接使比较T.ch[next[j]-1] 即 nextval[i]=nextval[j];
nextval[i]=nextval[j];
}
else
j=nextval[j];
}
}
下面来看一下用这个算法实际解决的一个问题吧。
用V替换S中出现的所有与T相等的不重叠的字串。
Status Replace(HString &S,HString T,HString V){
//用V替换S中出现的所有与T相等的不重叠的字串
int pos=1;
while(pos){//只要还能找到字串
pos=Index_KMP(S,T,pos);//从pos位置开始找 返回找到的位置
StrDelete(S,pos,T.length); //pos位置删除这个串T
StrInsert(S,pos,V); //pos位置插入串V
}
return OK;
}