参考资料:《数据结构(C语言版)严蔚敏著》
版权说明:未经作者允许,禁止转载。如引用本文内容,需标明作者及出处。如本文侵犯了您的权益,请联系我删除并致歉。
文章说明:如文章中出现错误,请联系我更改。如您对文章的内容有任何疑问,也欢迎来与我讨论。
本文正在施工中...请稍等...
基本概念
模式匹配:在串S中定位某个子串T的操作称为模式匹配,其中串S称为目标串,串T称为模式串。
匹配成功:在模式匹配过程中,若出现模式串T中的每个字符依次和目标串S中的一个连续的字符序列相等,则称匹配成功,否则称匹配不成功。
常见算法
Brute Force算法
从目标串S的第pos个字符起和模式串的第一个字符进行比较,若相等,则继续逐个比较后续字符;否则从目标串的下一个字符起再重新和模式串的字符进行比较。依次类推,直至模式串T中的每个字符依次和目标串S中的一个连续的字符序列相等。
int Index(SString S, SString T, int pos){
//返回子串T在主串S中第pos个字符之后的位置。若不存在,则返回0.
//其中,T非空,1≤pos≤StrLength(S)
i=pos; j=1;
while(i<=S[0] && j<=T[0]){
if(S[i]==T[j]) { ++i; ++j; } //继续比较后续字符
else { i=i-j+2; j=1; } //指针后退,重新开始匹配
}
if(j>T[0]) return i-T[0];
else return 0;
}
KMP算法
KMP算法是在BF算法基础上的一种改进算法,其改进在于:每当一趟匹配过程中出现字符比较不相等时,不需要回退目标串指针,而是利用已经得到的“部分匹配”的结果,计算出模式串指针最少可以回退到模式串中的哪个位置重新开始匹配。
理论推导(计算回退的位置)
假设目标串为,指针为,模式串为,指针为,此时匹配进行到比较和,发现二者不相等(“失配”),如下图1所示。
(图1)
下一时刻,根据KMP算法,假设回退到模式串中的位置,不回退,匹配从和比较重新开始。那么,应该要满足模式串的前个字符与目标串中之前的个字符依次相等,即:
① ,如下图所示。
(图2)
而在回退之前,已经得到的“部分匹配”的结果是,模式串的前个字符与目标串中之前的个字符依次相等,即:
② ,参见图1。
根据上式,取出模式串中之前的个字符,也有下式成立:
③ 。
根据式①和式③,得到以下等式:
④ 。
可以看出,值仅依赖于模式串,由此,我们便得到了的计算公式。
令,它表示当模式串中第个字符与目标串中相应字符“失配”时,在模式串中需要重新和目标串中该字符进行比较的字符的位置,也即指针回退到位置。定义如下:
通过上述定义,可以得到的值。通俗地解释一下:
首先,初始化。其次对于,的值等于,模式串子串的最长相等真前缀与真后缀的字符个数加1。举个例子看一下:
在求得模式串的函数之后,匹配可按如下进行:假设以指针和分别指示主串和模式中正待比较的字符,令的初值为pos,的初值为1。若在匹配过程中,,则和分别增1,否则,不变,而退到的位置重新开始比较,若相等,则指针各自增1,否则,再退到下一个值的位置,依次类推,直至下列两种可能:一种是退到某个值时字符比较相等,另一种是退到值为0。在这两种情况下,两个指针都要各自增1。下面给出KMP匹配算法。
int Index_KMP(SString S, SString T, int pos){
//利用模式串T的next函数求T在目标串S中第pos个字符之后的位置的KMP算法。
//其中,T非空,1≤pos≤StrLength(S)
i=pos; j=1;
while(i<=S[0] && j<=T[0]){
if(j==0 || S[i]==T[j]) { ++i; ++j; } //继续比较后继字符
else j=next[j]; //模式串指针后退
}
if(j>T[0]) return i-T[0]; //匹配成功
else return 0;
}
上面介绍了求函数的简单方法,但计算机执行起来效率较低,下面给出更为高效的算法。
对于,存在等式④成立,且不存在更大的值使其成立。我们考虑,如果有,那么自然有。如果,我们将看成是模式串,看成是目标串,由于有④式成立,且当前,那么需要回退模式串指针到,匹配从和比较重新开始,如果二者相等,那么说明在该模式串中有,且不存在大于的值满足该式,所以根据函数的定义有;如果二者不相等,那么依次类推,直至下列两种情况,一是和模式串中某个字符匹配成功,此时,二是模式串中不存在这样的字符,此时。
算法描述如下:
void get_next(SString T, int next[]){
//求模式串T的next函数值并存入数组next
i=1; next[1]=0; j=0;
while(i<T[0]){
if(j==0 || T[i]==T[j]) { ++i; ++j; next[i]=j; }
else j=next[j];
}
}
上述求的算法仍有改进的空间,在比较和出现不等时,即,需要回退模式串指针到,而如果,那么自然有,无需再次比较。所以不如在计算值时,就避免这样的情况出现,由直接回退到的位置,那么改进后的算法如下:
void get_next(SString T, int next[]){
//求模式串T的next函数修正值并存入数组next
i=1; next[1]=0; j=0;
while(i<T[0]){
if(j==0 || T[i]==T[j]){
++i; ++j;
if(T[i]!=T[j]) next[i]=j;
else next[i]=next[j];
}
else j=next[j];
}
}