Bootstrap

改进的字符串模式匹配算法——KMP操作(C语言)

作者不会画图,见谅。

前言

关于什么是KMP操作,详情见

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 只与模式

;