一般提起字符串的相关算法,就是几个基本的算法:赋值strcpy、求长strlen、联接strcat、比较strcmp和求子串substr。这5个操作相对来说都比较简单,构成了字符串的最小操作集,其他的算法都可以由这几个算法来实现。但是实际应用中,模式匹配index是应用非常广泛的字符串操作,我们倾向于不依赖其他的操作来实现它。
如下图,在目标字符串S中查找模式字符串T的最直白的做法就是:
1.分别用i、j指向字符串
2.依次遍历,S[i]==T[j]则i++、j++
3.S[i]!=T[j]则i回溯到目标字符串S的起始点开始比较,j回溯到T字符串的起点
这个过程很容易写成程序,如下:
/**
*功能: 在目标字符串中求模式字符串的位置
*参数: S——目标字符串(source string)
T——模式字符串(template string)
pos——目标字符串从pos开始查找
*返回: 查找成功,返回T在S中的第一个匹配位置
查找失败,返回-1
*其他: 2015/01/09 By Jim Wen Ver1.0
**/
int index( const char *S, const char *T, int pos )
{
int i=pos, j=0;
int sLen = strlen(S), tLen = strlen(T);
while (i<sLen && j<tLen)
{
if (S[i]==T[j])
{
i++;
j++;
}
else//匹配不成功,回溯,重新下一个匹配
{
j=0;
i=i-j+1;
}
}
if (j>=tLen)//查找成功
{
return i-tLen;
}
else//查找失败
{
return -1;
}
}一般匹配很容易理解也很容易实现,但是效率不高,T字符串过长时,S的回溯成本太高,想了解具体的原因可以自行查找资料,这里就不详述了。想提高效率,就想如何减少回溯次数,如下处理:
可以看到,1、2歩和一般匹配相同,不同的是第3歩,当S和T失配后,i并没有回溯,这时候j回溯到a。为什么这样做呢,注意了!!!
对于T=abcabcdef,在d处失配后一定可以确定S对应的a之前的字符序列一定是abcabc,也就是说不管S是什么,只要T匹配到d,那么一定可以确认S对应点之前的字符序列,进一步也就是说不管S是什么,只要T在d处失配,那么一定可以根据T本身推出下一步应该使用T中的哪一个字符和当前S的i处字符比较,决定失配后j的回溯位置的只是字符串T。
我们可以把这个关系描述成j=next[j],即在j处失配后下一个j的位置。
那么整个匹配过程如下:
1.分别用i、j指向字符串
2.依次遍历,S[i]==T[j]则i++、j++
3.S[i]!=T[j],则T向右滑动到j=next[j],继续S[i]和S[j]的比较,如果不等则再继续T向右滑动到j=next[j],继续S[i]和S[j]的比较,如此循环
4.一旦T向右滑动的太厉害(即j=0时和S[i]还不匹配,则j只能=-1了),则i++、j++
那么整个过程写程序程序如下(注意过程2和4合并了),
/**
*功能: 在目标字符串中求模式字符串的位置
*参数: S——目标字符串(source string)
T——模式字符串(template string)
pos——目标字符串从pos开始查找
*返回: 查找成功,返回T在S中的第一个匹配位置
查找失败,返回-1
*其他: 2015/01/09 By Jim Wen Ver1.0
**/
int kmp_index( const char *S, const char *T, int pos )
{
int i=pos, j=0;
int sLen = strlen(S), tLen = strlen(T);
int *next = new int[tLen];
get_next(T, next);
while (i<sLen && j<tLen)
{
if (j==-1 || S[i]==T[j])//注意j==-1的情况
{
i++;
j++;
}
else//匹配不成功,回溯,重新下一个匹配
{
j = next[j];
}
}
delete[] next;
if (j>=tLen)//查找成功
{
return i-tLen;
}
else//查找失败
{
return -1;
}
}如下图:
假设T[j]和S[i]失配,则下一个应该比较的是T[k]和S[i],则必然有
又由已知的匹配关系有
则对于T必然有:
可以看到,j移动的规则就是找到从t1开始的一段字符序列和tj前面的一段序列相同。
到这里,就找到T的内部关系了,下面只用T计算next数组:
如何计算next数组呢?以T作为目标字符串,同时以T作为模式字符串,如下图:
这里引入了t-1,表示j向右滑到头了,那么计算next数组和模式匹配的算法就非常相似了,如下:
1.初始时i=0,j=-1,next[i]=j,即next[0]=-1,就是当T[0]和主串S不匹配的时候那么这时候当前S[i]位置就是不匹配了
2.如果T[i]==T[j],
此时隐含T[i-1]=T[j-1],T[i-2]=T[j-2]......
那么就有
此时当然有next[i+1]=j+1
3.如果T[i]!=T[j],注意此时隐含
那么为了找到和ti相同的元素,向右滑动,求下一个满足条件的j
j=next[j]
4.向右滑动过头,j=-1,则next[i+1]=j+1=0
5.补充在2中,如果T[i+1]==T[j+1],则
S和T[i+1]失配时,按照2有S再和T[next[i+1]]=T[j+1]匹配,显然必然也是失配的,这时候必须再向下查找,即next[i+1]=next[j+1]
那么实际求取next的程序如下(合并2、4、5),是不是和模式匹配程序很相似呢?
void get_next( const char *T, int *next )
{
int i=0, j=-1;
int tLen = strlen(T);
next[0]=-1;
while (i < tLen-1)
{
if (j==-1 || T[i]==T[j])
{
i++;
j++;
if (T[i]!=T[j])
{
next[i] = j;
}
else
{
next[i] = next[j];
}
}
else
{
j = next[j];
}
}
return;
}完整源代码下载链接
原创,转载请注明来自http://blog.csdn.net/wenzhou1219
原文地址:http://blog.csdn.net/wenzhou1219/article/details/42583131