字符串匹配的改进算法——KMP算法(C/C++)
字符串匹配
关于什么是KMP操作,详情见
KMP的基本思想
一般方法的弊端:
下面可以不看~~感兴趣可以看看~~,总之就是说:一般方法在面对重复率极高的串时匹配效率极低~
首先,当主串为 00000000000000000000000000000000000000000000000000001 ,而模式串为 00000001 时,由于主串中前七个字符为 0 ,模式串中前52个字符为 0 ,每一轮比较都在模式串的最后一个字符处失配,此时需要将指针(游标) i (指示主串),回溯到 i-6 的位置上,并从模式串的第一个字符开始重新比较。整个匹配过程 指针(游标) i 需要回溯45次, while 循环需要执行 46 * 8 * (Index * m)。在 worst situation下 时间复杂度为O(m*n)。
问题在哪?!
每次失配之后,模式串偏移量太小,导致重复匹配。如果你看了上面那段废话,就可以理解为,指针每次失配之后都回溯至起始位置,导致效率低下!
解决办法:指针不再回溯,一次遍历主串与模式串匹配即可完成!!
数学分析
来一段数学分析,不感兴趣的同学下滑再下滑就行~~
正文开始:
指针如何不回溯?只需要将模式串失配后,沿着主串方向滑动一定的长度,使主串部分可以与模式串匹配,不需要回溯指针。
假设主串为S[i],模式串为P[j],其中i指示主串的位置即为主串指针(浮标),j指示模式串的位置即为模式串指针(浮标)。
在一次匹配之后,设第j个字符匹配失败,假设此时主串在指针(游标)i在不回溯的前提下,应与模式串中的第 k (k<j)个字符继续匹配;则一定会有如下关系式:
P[1]P[2]...P[k-1] = S[i-k+1]S[i-k+2]...S[i-1] ----(1)
这次匹配的前一次的匹配结果:
P[j-K+1]P[j-k+2]...P[j-1] = S[i-k+1]S[i-k+2]...S[i-1] ----(2)
联立式(1)和(2),消去主串可以得到:
P[1]P[2]...P[k-1] = P[j-K+1]P[j-K+2]...P[j-1]
根据此式子可以得出如下结论:
主串中第i位应与模式串中第 k(k<j) 位对齐,且 k 仅与模式串自身有关,因此,匹配仅需从模式串中第 k 个字符和主串中第 i 个字符比较起继续进行。
如上述所得:此时应求模式串的k值!为了确定k,需要一个next(){next[j]=k}来存储模式串的每一个字符对应的k值~
define next():
令next[j]=k,则next[j]表明当前模式中第j个字符与主串中相应字符“失配”时在模式中需重新和主串中该字符进行比较的字符的位置。
next[j] =
if(j==1) 0;
// Max集合非空
else if(Max{K|1<k<j && p[1]...p[k-1]=p[j-k+1]...p[j-1]})
Max{K|1<k<j && p[1]...p[k-1]=p[j-k+1]...p[j-1]
else 1; //其他情况
next数组详解
next的文字描述
废话又来了~不过可以看看~
首先,我们简要分析一下 next()的功能。
假设 i 指示主串,j 指示模式串, k 表示应滑动到的位置。
由我们上面得到的数学公式的结论:k 只与模式串自身有关。
P[1]P[2]...P[k-1] ”=“P[j-K+1]P[j-K+2]...P[j-1]
那么这个 k 具体又是什么东西呢?
简而言之,就是模式串在第 j 个位置(失配的位置)之前,有 k-1 个字符与模式串从首字符起 k-1 个字符相同,这也是上述数学公式的结论。
那么如何求得模式串的 k 呢?
注意:需要求解的是模式串的第 j-(k-1) 到 j-1 的位置与 1 到 k-1 的位置字符相同。
如果使用两层循环求解效率过低,在此处可以使用递归的思想。
即要求 k 这个位置,在每次比较,即模式串与模式串自己匹配(模式串既作为主串,也作为模式串),而我们的next()本身指示的就是模式串应该滑到哪个位置。
所以,应该使用两个指针指示模式串,假设分别为 i ,j 。i 依次指向模式串的每一个位置, j 指向模式串头地址并听从 next()本身滑动。根据上述定义,模式串的首字符的 next 值应为0。i从第 next[1] 开始,j 从next[0]开始。
当 i 指示到模式串中间某个位置时,j 也指示到了模式串中的某个位置(j<i),此时对于next()应有两个选择:一、当前位置下,next[i] == next[j],那么只需要将指针顺移,并且该位置(i指示的位置)的next值就为 j 的值(前面一共判断相同判断了j次)。二、当前位置下,next[i] != next[j],此时,根据next()的定义,j 应该回溯到 next[j] 的位置(这里使用了递归)。
现在从初始状态检查 next[ ]。已知next[1] = 0。初始 j = 0,i = 1 ,但next[0]并不存储数据,但是又要为next[1]赋值,避免使用冗长的判断,赋值,则在判断 next[i] 是否等于 next[j] 时,当 j=0 ,直接递增 i,与 j(next[0] 并没有实际意义,next[1] 才有实际意义)。
(这里只是在求模式串的k值,不要与主串,模式串的i ,j搞混,虽然本质上没有区别)
next实现:
next()的C语言代码如下:
// 求模式串T的next函数值并存入数组next。
void get_next(char *T, int **next){
int i=0,j=-1,T_len = strlen(T);
(*next) = (int *)malloc(T_len*sizeof(int));
(*next)[0] = -1;
while(i<T_len-1){
if(j==-1||T[i]==T[j]){
i++;j++;
(*next)[i] = j;
}
else
j = (*next)[j];
}
//for(int i=0;i<strlen(T);++i)
// printf("%d ",(*next)[i]);
//putchar('\n');
}
KMP算法C语言实现
代码如下:
// 从主串S的第pos个字符后开始匹配即数组的pos-1个位置
int Index_Kmp(char *S,char *T,int *next,int pos){
int i=pos-1,j=0;
int S_len = strlen(S),T_len = strlen(T);
while(i<S_len && j<T_len){
if(j==-1 || S[i]==T[j]){
++i;
++j;
}
else
j = next[j];
}
if(j>=T_len)
return i-T_len;
else
return -1;
}

浙公网安备 33010602011771号