子串的定位操作通常称做串的模式匹配,这也是串的一个很要的操作。
一,一般定位子串位置算法
算法基本思想:从主串的第1个字符起和模式的第一个字符进行比较,若相等,则再比较主串和模式串的后续字符。否则将主串的后续字符和模式串的第一个字符进行比较,在网上找的一张图片:
int index(String S,String T,int pos){
//返回子串T在主串S中第pos个字符之后的位置。若不存在,则函数返回0;
int i=pos,j=0;
while(i<=S.length&&j<=T.length){
if(S[i] == T[j]){
i++; // 比较后续字符
j++
}else{
i=i-j+1; //重新开始匹配
j=0;
}
}
if(j>T.length)
return i-T.length; // 找到返回
else
return 0; // 没有找到
}
1,若过程中比较失败,子串从头开头重新匹配j=0;,而主串从子串已经比较的后续字符开始,i=i-j+1。
2,若过程中比较一个字符成功之后,两个串都移至后续字符,i++,j++。
3,若结果查找成功,那么j的值一定比T.length大1,并且i-T.length就是查找成功之后在主串的位置。否则就是查找失败。
4,可以知道这种算法在最坏的时候,时间复杂度为O(T.length*S.length).
二,KMP算法
这是一种改进的模式串匹配算法,和前一种算法相比,该算法可以在O(T.length+S.length)时间复杂度内完成。这种改进的思想主要是:每当一趟匹配过程中出现的字符不等时,我们不需要将i的值重新设置为i=i-j+1; 而是利用之前已经得到的一部分匹配【就是前面j个字符已经相等】的结果,将模式向右移动尽可能远的一段距离,然后再进行比较,这样i的值就可以不用移动,只要移动j的值就可以了。
换句话说就是当主串的第i个字符与子串的第j个字符不相等时,主串的第i个字符如果不回溯的话,应该与子串模式中的哪个字符再进行比较。
下面讨论怎么做到这一点的。
算法图示:
算法分析
2.1 主串的第i个字符接下来与模式串的哪个字符进行比较
假设模式和主串正在比较第K个字符,那么前面K-1个字符必然已经比较成功,也就是满足这个关系:
这里所要表达的意思就是:若模式串中存在满足上面3中条件的两个子串,则当匹配过程中,主串中第i人字符与模式中第j个字符比较不相等时,只需要将模式向右移动至模式中第k个字符和主串中第i个字符对齐,因为模式串中的头k-1个字符肯定与主串中的第i个字符 之前的k-1个字符相等,所以,匹配只需要从模式中第k个字符与主串中第i个字符比较,就可以进行后续比较操作。我们定义若模式串中第j个字符不相等,然后从第k个字符开始比较这种关系,即next[j] = K .
2.2 next[j] = K 求解
next[j] 的定义如下【这是数据结构书籍上的定义】
假定下标从1开始。
假设next[j]=k,这表明存在"P1…Pk-1”=“Pj-k+1…Pj-1"这样的关系,此时next[j+1]的值如何计算呢?分下面两种情况:
1)若Pk=Pj,则有“P1…Pk-1Pk”=“Pj-k+1…Pj-1Pj” ,如果在j+1发生不匹配,说明next[j+1] = k+1 = next[j]+1。
2) 若Pk≠Pj,可把求next值问题看成是一个模式匹配问题,整个模式串既是主串,又是子串:
(1)若Pk’=Pj,则有“P1…Pk’”=“Pj-k’+1…Pj”,next[j+1]=k’+1=next[k]+1=next[next[j]]+1.
(2)若Pk”=Pj ,则有“P1…Pk””=“Pj-k”+1…Pj”,next[j+1]=k”+1=next[k’]+1=next[next[k]]+1.
......
(n)当无任何k'满足等式时,next[j+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[i]== T[j]) // j=0 ,可以表示为一个分界符一样,就是每当j=0时,都可以表示找不到任何的子串可以向右高效的滑动。
{
++i; ++j; next[i] = j;
}
else
j= next[j];
}
}
这里定义的next函数有一个缺陷,例如模式aaaab在和主串aaabaaaab匹配时,当i=4,j=4时,就是主串中的b字符和模式中的第四个a字符不相等时,此时再将i=4与j=next(4)=3进行比较,然后再与j=2进行比较,其实,在这里模式中的串前面几个字符是相等的,因此没有必要多次移动,只需要直接与模式串中第一个字符进行比较就可以了。
void get_nextval(String T,int nextval[]){
int i=1;
nextval[1]=0;
j=;
while(i<T.legnth){
if(j==0 || T[i]==T[j]){
i++;
j++;
if(T[i] == T[j]){
nextval[i]=nextval[j];
}else{
nextval[i]=j;
}
}else{
j=nextval[j];
}
}
}
int index_kmp(Stirng S,String T,int pos){
int i=pos;
int j=1;
while(i<=S.length && j<=T.length){
if(j==0 || S[i] == T[j]) {
i++;
j++;
}else{
j=next[j];
}
if(j>T.length)
return i-T.length;
else
return 0;
}
可以看到这和一般的匹配算法,有两个区别:
1)匹配失配后,主串不是重新回溯,而是将子串往右滑动,以找到一个合适的点,再进行比较。
2)当比较字符成功或者是包含当前位置的子串不存在时,将主串移至下一个位置。