KMP算法

KMP可以在O(n)O(n)的时间内解决两个字符串匹配问题。

引出KMP

假定两个字符串:
模式串SSABABABABAAABABABABAA
模板串PPABABAAABABAA

kmp1
我们可以发现在第6个位置发生了失配,依照暴力想法,是移动pp串到下一个位置。但发现移动之后第一个就不匹配,继续移动。
kmp2
我们发现第三次在8发生了失配,如果继续按照朴素想法那又得在下一个位置重头从模式串匹配。那我们有没有什么方法能快速使得字符串从图一的失配位置直接跳到图二的位置呢。这时就引入KMP算法。

前缀函数

在引入KMP算法之前,我们观察两幅图的跳转位置,ABABAABABA->ABAABA,发现什么了吗,ABAABAABABAABABA的非平凡前缀(最后一个字符以外,一个字符串的全部头部组合)和非平凡后缀(除了第一个字符以外,一个字符串的全部尾部组合)的最长共有元素的长度。
前缀函数就是满足此定义:ne[i]ne[i]s[0..i]s[0..i]最长的相等的非平凡前后缀的长度。

字符串 前缀集合 后缀 ne
A 空集 空集 0
AB {A} {B} 0
ABA {A,AB} {A,BA} 1
ABAB {A,AB,ABA} {B,AB,BAB} 2
ABABA {A,AB,ABA,ABAB} {A,BA,ABA,BABA} 3
ABABAA {A,AB,ABA,ABAB,ABABA} {A,AA,BAA,ABAA,BABAA} 1

那我们如何求解nene数组呢?
nene数组的求法是通过模板串自己与自己进行匹配操作得出来的
ne
当我们求ne[5]ne[5]时,此时已经求的ne[4]=2ne[4]=2,此时i=5i=5j=2j=2p[i]=p[j+1]p[i]=p[j+1],当前字符匹配,可以直接通过ne[4]ne[4]转移过来,ne[5]=ne[4]+1=3ne[5]=ne[4]+1=3

接下来求ne[6]ne[6]时,此时i=6i=6j=3j=3p[i]!=p[j+1]p[i]!=p[j+1],当前字符不匹配,那么我们要根据nene数组将已经匹配的字串ABAABA移动到其最大前后缀AA的位置ne[j]ne[j],此时j=1j=1,p[j+1]=Bp[j+1]=B也不等于s[i]s[i],继续移动ne[j]ne[j],此时j=0j=0p[j+1]=s[i]p[j+1]=s[i]ne[6]=ne[j]+1=1ne[6]=ne[j]+1=1

求解nene数组代码:

for(int i = 2, j = 0; i <= n; i ++)
        {
            while(j && p[i] != p[j + 1])//失配, 移动p串
                j = ne[j];
            if(p[i] == p[j + 1])//当前元素匹配,j移向p串下一位
                j ++;
            ne[i] = j;//匹配成功,继续匹配下一个子串
        }

匹配代码:

 for(int i = 1,j = 0; i <= m; i ++)
        {
            while(j && s[i] != p[j + 1])
                j = ne[j];
            if(s[i] == p[j + 1])     j ++;
            if(j == n)
                {
                    printf("%d ", i - n);
                    j = ne[j];
                }
        }

Q.E.D.