KMP模式匹配算法详解

这里主要对前面的KMP模式匹配算法中有必要进行详解的地方进行一个讲解:

void get_next(String T,int *next)
{
    int i,j;
    i=1;
    j=0;
    next[1]=0;
    while(i

首先需要明确:过于深的问题咱不考虑,我们需要的是把这个算法弄明白然后能用就是最好。
把程序运行的过程推导一遍:

第一个实例

KMP模式匹配算法详解_第1张图片
①:i=1,j=0, next[1]=0
这是程序中最开始的初始化过程,很容易理解,但是需要注意后面的推导都是依赖在这一步之上。
②:理解判断语句:j==0 的作用:
(1)开始时 j 是为0的,需要进入到循环中,因为只有这样才能使得 i 和 j 都加一,然后真正进行第一次字符的判断。
(2)后面 j 是可能回溯到0的,但还是需要进入循环进而进行后面得到判断。
总之:这个判断语句就是为了当 j 为0的时候(有两种情况:一种是开始的时候,一种是后面回溯到0时),能够使得后面的判断还能够继续进行而设立的的。
③:理解 j 值的作用:
(1)T[j]表示前缀的单个字符
(2)j 还可以表示第 i 个字符之前的重复度:n
注意这个地方很难理解,可以看到上面的推导图中每一个箭头上面的 j+1,其实在这个时候就是把 j 当做重复度 n 然后执行前面的规则(next数组元素的值为重复度n加一所得)
(3)然后我把上面的程序稍稍改变一下,再进一步理解这个 j 实现的作用。

i=i+1;
next[i]=j+1;
j=j+1;

这样就方便理解上面所说的把 j 看做重复度 n 然后执行加一导入next数组的规则;并且下面的 j=j+1,更能够形象的体现 j 作为用于比较的前缀字符T[j] 的下标的功能。
(4)那么现在问题来了:如何理解 j=next[j] 这条语句呢?
这个语句是最重要的 j 值的回溯语句, 跟后面的实际比较S串与T串的程序中的 j 值的回溯是一个原理。
在这个事例中,先只需要理解它的一个简单含义:这里 j 值都是从1回溯到0,既然前面说过这个 j 值有重复度的含义,那么在这个地方可以暂且理解成:因为经过比较发现 T[i] 之前的串中没有重复的部分,那么它的重复度自然需要计为0。
更具体的将在后面的实例中解释。
④:比较一下通过代码推导出来的next数组与之前通过规则得到的数组。

第二个实例:

KMP模式匹配算法详解_第2张图片
理解这个实例主要是需要分析j=next[j] 这个回溯原理:
需要知道:

  • 右边的 j 的含义
  • 这个时候next[j] 的含义
  • j=next[j]

(1)举个例子:abc abc abc abc abc abc abc ax 与 abc abc abc abc abc abc abc abx。前面当 i=23时,j=20,后面的 i=24时,j=21,可以发现 i 与 j 一定相差的值为重复串的长度,并且 j 与 i 一定在重复串中对应相同的位置;然后前面的 j 会从20回溯到17,14…一直到2,最后到1,而后面的 j 会从21,回溯到18,15…最后到3,1;j 每次回溯的位置依然是重复串中对应相同的位置。
(2)所以上面的三个含义,不知道大家有没有get到。

  • j就是距离比较串最近的重复串中与 i 对应的位置
  • 每一个next[j]都对应的是距离当前 j 最近的重复串中与 j 对应的位置;
  • j=next[j]明显就是在执行回溯功能

(4)需要注意两个极端的情况:

  • 如果比较串前面如果没有重复串,比如上面的abcdex这种类型,它每一次执行回溯其实表示的是在将 j归0, 表示将要将 i 加1,然后与第一个字符进行比较。
  • 如果串本身重复度为100%,像aaaaaaaab这样的情况,发现当 i 为9时,j为8,当i为8时,j 为7,这样永远是相邻的,然后它回溯时是在一个一个向前回溯;就可以在i=9时,b作为比较串,前面相邻的 j=8位置上的a是重复串(相当于把上面的abc换成单个a),因为只有串一个字符所以他们的位置一定是对应的所以还是可以按照上面所说的规律理解回溯。

(5)值得注意的是最后的一次执行 j=next[j] 表示的是将要从新开始比较,而不是返回到对应的位置。
(6)但其实上面的回溯明显有多余的操作:比如第一个串,那些返回的位置上一定是相等的,这样一直比较肯定得到的是不相等的结论,所以可以直接到1,因此有了后面的改进算法。

在这个里面想要给大家理清几个概念:

①:前缀与后缀:前缀与后缀在这里一定不能仅仅简单的认为前面和后面没有交集,这里是可以有交集的,可以通过数学的角度来证明:
列一个不等式:j-k+1<=k-1 可以得到 k=>(1/2)j+1,然后k又满足2,也就是说至少要有3个,就可以满足前后缀有交集的情况,例如:aaa
②:但是前缀与后缀不能都表示的是整个串本身:如果这样,则 j-k+1=1,j-1=k-1,然后可以推导出 j=k的结论,但是很明显这样违背了定义。
③:KMP模式匹配算法改进与不改进,它们的时间复杂度都是O(m+n):
首先需要知道在计算时间复杂度的时候这里是忽略了回溯步骤,前面的朴素匹配也是一样(这一点我暂时还不明白为什么…);然后因为推导T串的next数组这个过程时间复杂度为T串的长度(忽略了回溯过程),所以是O(m);而S串与T串的比较过程去掉了回溯,也是相当于在从S串的头走到尾,也就是S串自身的长度O(n),所以两个部分相加就是O(m+n);而由于改进算法只是在回溯上的改进,当然就对时间复杂度没有影响。
④可以列一个方程比较一下朴素匹配的(n-m+1)xm与这里m+n的大小。

你可能感兴趣的:(学习数据结构记录)