KMP算法

来源:互联网 发布:高清网络监控系统方案 编辑:程序博客网 时间:2024/06/03 22:39

KMP 算法,俗称“看毛片”算法,是字符串匹配中的很强大的一个算法,不过,对于初学者来说,要弄懂它确实不易。整个寒假,因为家里没有网,为了理解这个算法,那可是花了九牛二虎之力!不过,现在我基本上对这个算法理解算是比较透彻了!特写此文与大家分享分享!

我个人总结了, KMP 算法之所以难懂,很大一部分原因是很多实现的方法在一些细节的差异。怎么说呢,举我寒假学习的例子吧,我是看了一种方法后,似懂非懂,然后去看另外的方法,就全都乱了!体现在几个方面: next 数组,有的叫做“失配函数”,其实是一个东西; next 数组中,有的是以下标为 0 开始的,有的是以 1 开始的; KMP 主算法中,当发生失配时,取的 next 数组的值也不一样!就这样,各说各的,乱的很!

所以,在阐述我的理解之前,我有必要说明一下,我是用 next 数组的, next 数组是以下标 0 开始的!还有,我不会在一些基础的概念上浪费太多,所以你在看这篇文章时必须要懂得一些基本的概念,例如  朴素字符串匹配 ”“ 前缀  ,  后缀  等!还有就是,这篇文章的每一个字都是我辛辛苦苦码出来的,图也是我自己画的!如果要转载,请注明出处!好了,开始吧!

假设在我们的匹配过程中出现了这一种情况:

根据 KMP 算法,在该失配位会调用该位的 next 数组的值!在这里有必要来说一下next 数组的作用!说的太繁琐怕你听不懂,让我用一句话来说明:

返回失配位之前的最长公共前后缀!

好,不管你懂不懂这句话,我下面的文字和图应该会让你懂这句话的意思以及作用的!

首先,我们取之前已经匹配的部分(即蓝色的那部分!)

我们在上面说到 next 数组的作用时,说到  最长公共前后缀  ,体现到图中就是这个样子!

接下来,就是最重要的了!

没错,这个就是 next 数组的作用了 :

返回当前的最长公共前后缀长度,假设为 len 。因为数组是由 0 开始的,所以 next数组让第 len 位与主串匹配就是拿最长前缀之后的第 1 位与失配位重新匹配,避免匹配串从头开始!如下图所示!

(重新匹配刚才的失配位!)

 

如果都说成这样你都不明白,那么你真的得重新理解什么是 KMP 算法了!

 

接下来最重要的,也是 KMP 算法的核心所在,就是 next 数组的求解!不过,在这里我找到了一个全新的理解方法!如果你懂的上面我写的的,那么下面的内容你只需稍微思考一下就行了!

 

跟刚才一样,我用一句话来阐述一下 next 数组的求解方法,其实也就是两个字:

继承

a 、当前面字符的前一个字符的对称程度为 0 的时候,只要将当前字符与子串第一个字符进行比较。这个很好理解啊,前面都是 0 ,说明都不对称了,如果多加了一个字符,要对称的话最多是当前的和第一个对称。比如 agcta 这个里面 t 的是 0 ,那么后面的 a 的对称程度只需要看它是不是等于第一个字符 a 了。

b 、按照这个推理,我们就可以总结一个规律,不仅前面是 0 呀,如果前面一个字符的 next 值是 1 ,那么我们就把当前字符与子串第二个字符进行比较,因为前面的是1 ,说明前面的字符已经和第一个相等了,如果这个又与第二个相等了,说明对称程度就是 2 了。有两个字符对称了。比如上面 agctag ,倒数第二个 a 的 next 是 1 ,说明它和第一个 a 对称了,接着我们就把最后一个 g 与第二个 g 比较,又相等,自然对称成都就累加了,就是 2 了。  

c 、按照上面的推理,如果一直相等,就一直累加,可以一直推啊,推到这里应该一点难度都没有吧,如果你觉得有难度说明我写的太失败了。

当然不可能会那么顺利让我们一直对称下去,如果遇到下一个不相等了,那么说明不能继承前面的对称性了,这种情况只能说明没有那么多对称了,但是不能说明一点对称性都没有,所以遇到这种情况就要重新来考虑,这个也是难点所在。

如果蓝色的部分相同,则当前 next 数组的值为上一个 next 的值加一,如果不相同,就是我们下面要说的!

如果不相同,用一句话来说,就是:

从前面来找子前后缀

1 、如果要存在对称性,那么对称程度肯定比前面这个的对称程度小,所以要找个更小的对称,这个不用解释了吧,如果大那么就继承前面的对称性了。

2 、要找更小的对称,必然在对称内部还存在子对称,而且这个必须紧接着在子对称之后。

 

如果看不懂,那么看一下图吧!

好了,我已经把该说的尽可能以最浅显的话和最直接的图展示出来了,如果还是不懂,那我真的没有办法了

KMP算法的优化(只适用于在原串中找出一个模式串)

KMP算法next数组计算只关注了前k-1个字符中,前后匹配的子串,没有利用到当前失配的字符;

比如:ABACA,当第2个A失配时,说明被匹配串的当前位置的字符必定不等于A,所以将第0位对齐到此位也必定失配,所以应该继续回溯到第0位失配时所需要对齐的位,这里也就是-1;

这个"必定不等于(!=)"是可以被利用的!我们对KMP算法next数组计算的优化正是基于此;

我们以一个例子来说明。譬如我们给的P字符串是“abcdaabcab”,经过KMP算法,应当得到“特征向量”如下表所示:

下标i 0 1 2 3 4 5 6 7 8 9

p(i) a b c d a a b c a b

next[i] -1 0 0 0 0 1 1 2 3 1

但是,如果此时发现p(i) == p(k),那么应当将相应的next[i]的值更改为next[k]的值。经过优化后可以得到下面的表格:

下标i 0 1 2 3 4 5 6 7 8 9

p(i) a b c d a a b c a b

next[i] -1 0 0 0 0 1 1 2 3 1

优化的next[i] -1 0 0 0 -1 1 0 0 3 0

(1)next[0]= -1 意义:任何串的第一个字符的模式值规定为-1。(2)next[j]= -1 意义:模式串T中下标为j的字符,如果与首字符相同,且j的前面的1—k个字符与开头的1—k个字符不等(或者相等但T[k]==T[j])(1≤k<j)。如:T=”abCabCad” 则 next[6]=-1,因T[3]=T[6](3)next[j]=k 意义:模式串T中下标为j的字符,如果j的前面k个字符与开头的k个字符相等,且T[j] != T[k] (1≤k<j)。即T[0]T[1]T[2]。。。T[k-1]==T[j-k]T[j-k+1]T[j-k+2]…T[j-1]且T[j] != T[k].(1≤k<j);(4) next[j]=0 意义:除(1)(2)(3)的其他情况。

void getNext(char* Pstr, int* Next){    int plen = strlen(Pstr) - 1;    int i = 0, j = -1;    Next[0] = -1;    while(i < plen)    {        //i位置的字符和j位置的字符相等时说明        //i+1位置的字符失配的话那么就可以直接比较j+1位置的字符了        //j == -1时只有可能是第一次,或者是与第一个字符不相等时才有可能        //所以也可以直接让失配后从0位置重新开始匹配        if(j == -1 || Pstr[i] == Pstr[j])        {            ++j;            ++i;            //i和j自增之后就是i-1位置的字符和j-1位置的字符相等            //按理说这里i失配的话就可以直接跳到j位置了            //但是这里有个优化。防止aaaaaaaab这种情况            //那就是如果i位置的字符如果和j位置相等的话            //说明i位置如果失配的话,及时跳到j位置还是会失配。            //所以i位置失配的话可以直接跳到j位置失配时要跳到的位置            if(Pstr[i] == Pstr[j])                Next[i] = Next[j];//优化关键,如果是在原串中找模式串出现的次数,删除这个if即可            else                Next[i] = j;        }        else            j = Next[j];    }}void KMP(char* Sstr, char *Pstr, int *Next){    int n = 0;    int slen = strlen(Sstr);    int plen = strlen(Pstr);    int i = 0, j = 0;    for(; i < slen;)    {        if(j == -1 || Sstr[i] == Pstr[j])        {            ++j;            if(j == plen)            {                j = Next[j - 1];                ++n;            }            else                ++i;        }        else            j = Next[j];    }    printf("%d\n", n);}




0 0
原创粉丝点击