很久以前就学过KMP,不过一直没有深入理解只是背代码,今天总结一下KMP算法来加深印象。
一、KMP算法介绍
- KMP解决的问题:给你两个字符串A和B(|A|=n,|B|=m,n>m),询问一个字符串在另一个字符串中的每一次出现位置。
- 暴力:枚举长串中的每一个起点,然后一位一位判断是否与短串完全相同,枚举复杂度是O(n),比较的复杂度是O(m),总的时间复杂度是O(nm),时间复杂度比较差
- 引入两个定义:
1.匹配串(A):被匹配的长串。
2.模式串(B):在匹配串中每次找出现位置的短串。
- 在匹配的过程中,我们相当于是要每次找到匹配串的一个前缀的后缀与模式串的一个前缀完全相同,也就是说,我们需要在匹配串上维护一个指针i,在模式串上维护一个指针j,使得Ai-j+1~i = B1~j 。
举个例子:匹配串为abbabab,模式串为abbaa,当A[i]==B[j]时直接往后继续找,当i=5,j=5时会产生失配,这时按照我们暴力的想法,我们会将i退回的到2位置,将j退回到1的位置,然后重复此过程,然而我们考虑一下就会发现这一段是一个字符也匹配不上的,因为A1~5已经和B1~5匹配上了,如果A2~6可以和B1~5匹配,则说明A1~5与A2~6完全相同,然而显然不同,于是我们有了新的思路,当我们失配时,我们缩小j,直到A的以i为结尾的前缀的后缀与B的长度为j的前缀完全相同,也就是说找到一个最大的k使得Ai-k+1~i=B1~j(k<j)。那么如何快速找到k呢?
- 这里我们引入next数组,next[j]表示模式串中长度为j-1的前缀中最长的前缀等于后缀的长度,那么当A[i]与B[j]失配时j需要往前移j-next[j]位,显然next[1]=0。
- 如何求出next数组?自己与自己匹配,如果可以匹配上,即Bnext[j-1]+1=Bj,那么next[j]=next[j-1]+1,如果不能匹配上,就让next往回跳直到可以匹配。(如果不理解可以自行画图模拟,很容易就可以理解。)
- KMP算法的大致思路就介绍完了,就是先让模式串自己与自己匹配求出next数组,然后用next数组辅助与匹配串匹配。时间复杂度为O(N+M)。
代码(洛谷P3375):
#include<bits/stdc++.h> using namespace std; const int MAXX=1e6+10; char s1[MAXX],s2[MAXX]; int l1,l2,next[MAXX]; //next[i]表示s2中以i为结尾的非前缀子串与A的前缀能够匹配的最大长度 void KMP() { for(int i=1,j=0;i<l2;i++){ while(j&&s2[i]!=s2[j]) j=next[j];//匹配不到就往下找 if(s2[i]==s2[j])j++; next[i+1]=j; } for(int i=0,j=0;i<l1;i++){ while(j&&s1[i]!=s2[j])j=next[j]; if(s1[i]==s2[j])j++; if(j==l2){ //找到一次出现 printf("%d\n",i-l2+2); } } } int main() { cin>>s1;cin>>s2; l1=strlen(s1);l2=strlen(s2); KMP(); for(int i=1;i<=l2;i++) printf("%d ",next[i]); return 0; }