字符串匹配中经常会用到KMP算法。它求解的问题类型是:字符串匹配。给你两个字符串,寻找其中一个字符串是否包含另一个字符串,如果包含,返回包含的起始位置。
我们一般的做法是:将一个字符串(长度为n,模式串)放在另一个字符串(长度为m,主串)开始的位置,然后依次比较,如果有不匹配的字符,就将字符串往后移一位,然后依旧从头开始比较。直到找到完全匹配的位置,返回。遍历顺序从0到m-n,每次遍历,都要比较n(最多)次,所以复杂度近似为O(m*n);
而我们今天说的这个KMP算法,可以达到O(m+n)的复杂度。
算法基本思想:每次当匹配失败时,没有必要就移动一位,因为前面可能有已经匹配的字符了。如果模式串当前的字符存在公共前后缀,那么我可以对于每一个模式串事先计算出模式串的内部匹配信息,在匹配失败时最大的移动模式串,以减少匹配次数。
注意最长前缀:是说以第一个字符开始,但是不包含最后一个字符
KMP有个next数组,用来存放模式串中内部的匹配信息。举个例子,比如模式串为ababaca,长度是7,所以next[0],next[1],next[2],next[3],next[4],next[5],next[6]分别计算的是 a,ab,aba,abab,ababa,ababac,ababaca的相同的最长前缀和最长后缀的长度。由于a,ab,aba,abab,ababa,ababac,ababaca的相同的最长前缀和最长后缀是“”,“”,“a”,“ab”,“aba”,“”,“a”,所以next数组的值是[-1,-1,0,1,2,-1,0],这里-1表示不存在,0表示存在长度为1,2表示存在长度为3。这是为了和代码相对应。
当求出模式串的next数组之后,我们就要进行KMP算法的匹配了。
#include <iostream> #include <string> #include <vector> using namespace std; void cal_next(string str,vector<int>&next) { int len=str.size(); next[0]=-1; int k=-1; for(int i=1;i<len;i++) { while(k>-1 && str[k+1]!=str[i]) { k=next[k]; } if(str[k+1]==str[i]) { k++; } next[i]=k; } } int KMP(string str,string ptr,vector<int>&next) { int s=0,p=0; while(s<str.size() && p<ptr.size()) { if(str[s]==ptr[p]) { s++; p++; } else { if(p==0) s++; else p=next[p-1]+1; } } return (p==ptr.size())?(s-ptr.size()):-1; } int main() { string str,ptr; cin>>str>>ptr; vector<int> next; next.resize(ptr.size(),0); cal_next(ptr,next); cout<<KMP(str,ptr,next)<<endl; return 0; }
程序的关键在于如何理解next的作用,以及如何维护它。