改进的字符串模式匹配算法——KMP操作(C语言)
前言
关于什么是KMP操作,详情见
对于本文使用的数据结构,见
一、一般的模式匹配算法
int Index_S (SString S,SString T,int pos){
int i,j;
i = pos;
j=1;
while(i<= S[0] && j <= T[0]){
if(S[i] == T[i]){
++i; // 继续比较后继
++j;
}
else{
i = i-j+2; // 指针后退重新开始匹配,指针后退至前一次的pos+1
j = 1; // +2 -> j 从1开始,i-j为前一次pos的前一个位置
}
}
if(j>T[0])
return i-T[0];
else
return 0;
}
对于一般的模式匹配算法,在处理文本文件时,由于文本内容重复出现的子串不多,因此一般方法的时间复杂度可以近似的看做O(m+n)
。当处理一些01串或者字符重复较多的文本,例如 00000000000000000000000000000000000000001
或
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaab
时,一般方法就显得愚笨不堪。
一个文本主串,一般算法的循环次数为:
Index+T[0]-1+重复比较字符
近似的可以看做m+n次。
二、KMP算法的基本思想
1.一般方法的弊端
首先,当主串为 00000000000000000000000000000000000000000000000000001
,而模式串为 00000001
时,由于主串中前七个字符为 0
,模式串中前52个字符为 0
,每一轮比较都在模式串的最后一个字符处失配,此时需要将指针(游标) i
(指示主串),回溯到 i-6
的位置上,并从模式串的第一个字符开始重新比较。整个匹配过程 指针(游标)i
需要回溯45次, while
循环需要执行 46 * 8 * (Index * m)
。在 worst situation下 时间复杂度为O(m*n)
。
2.解决想法
指针不再回溯,一次遍历主串与模式串匹配即可完成。
3.数学解释
指针如何不回溯?只需要将模式串失配后,沿着主串方向滑动一定的长度,使主串部分可以与模式串匹配,不需要回溯指针。
假设主串为S[i],模式串为P[j]
一次匹配之后,设第j个字符匹配失败,假设此时主串在指针(游标)i
在不回溯的前提下,应与模式串中的第 k (k<j)个字符继续匹配;
则一定会有如下关系式:
“P[1]P[2]…P[k-1] ”=“ S[i-k+1]S[i-k+2]…S[i-1]”
前一次的匹配结果:
“P[j-K+1]P[j-k+2]…P[j-1] ”=“ S[i-k+1]S[i-k+2]…S[i-1]”
联立上式,消去主串,得到:
“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个字符比较起
继续进行。
主串中第i位于模式串中第 k ( k < j)位对齐,且k 仅与模式串自身有关
为了确定K
,需要一个next(){next[j]=k}
来存储模式串的每一个字符对应的K
define next():
令next[j]=k,则next[j]表明当前模式中第j个字符与主串中相应字符“失配”时
在模式中需重新和主串中该字符进行比较的字符的位置。
next()
的数学定义如下:
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; //其他情况
4.next()详解
next()
的主要功能是:指示模式串与主串失配之后,模式应该滑到什么地方继续与主串匹配。
(1)解剖next()函数
首先,我们简要分析一下 next()
的功能。
假设 i 指示主串,j 指示模式串, k
表示应滑动到的位置。
由我们上面得到的数学公式的结论:k
只与模式