KMP这个算法原理的学习已经搁置了一年了,现在下定决心搞懂它。因为比较难,所以特地开辟几篇博客来,以后绝对不能只套板子,要深刻理解原理。
参考链接:http://blog.csdn.net/yutianzuijin/article/details/11954939/
首先,我们的目标是在O串中寻找f。
对于暴力匹配法,就是把f从O的开头开始匹配,遇到不匹配的就把f往后挪1位重新匹配。
而对于kmp算法,遇到失配的情况,不仅仅要把f往后挪1位,而是要把f往后挪很多位。不仅如此,还不用重新匹配,而是继续匹配。
下面这个图非常好,假设f串向后挪了k位。说明什么呢?说明A=B,A是f的前缀,B是失配位之前的后缀。
我们如果令next[i]表示f[0..i-1]最长的相等的前缀和后缀(串本身除外),那么每次向后移动的这个k就等于i-next[i]。
首先,这样挪之后,后面可以正常匹配是很显然的。那么问题是,这样挪,就不会漏掉中间的一些吗?被跳过的那些位就一定没有能匹配的吗?
答案是没有。可以用反证法来证明。假设有一个位置t它能匹配。类似于下面这个图。
我们发现,有一个更长的前缀和后缀相等了,与next[i]是最长的相等前缀后缀矛盾。因此中间漏掉的情况是不存在的。
所以我们可以放心大胆的每次失配,f串向后移动i-next[i]位,然后继续匹配失配位。
那么这里有一个问题,当匹配完整个f之后,下次该往哪走呢?(假如我们不仅仅满足于找到一个匹配位置,而是要找出所有匹配位置)
其实这里只需要想:匹配完一个串以后,下次可能再匹配的位置在哪里呢?其实把f往后挪l-next[l],其中l是f串的长度。
那么现在有一个问题了,上面说的把f往后挪i-next[i],实际上在编程实现的时候并不这样写。假设i是O的匹配指针,j是f的匹配指针,那么我们想把j往后挪j-next[j],其实就是j=next[j],下次继续让i和j匹配。
下面是假设我们已经得到了next数组,来求f串在O串中出现的次数和位置的代码。
int nxt[1000]; vector<int> P; int KMP(char f[],int n,char O[],int m) { int cnt=0; P.clear(); getnext(f,n,nxt); int nowi=0,nowj=0; while (nowi<m) { while (nowj!=-1 && O[nowi]!=f[nowj]) nowj=nxt[nowj]; nowi++,nowj++; if (nowj==n) { cnt++; P.push_back(nowi-nowj); nowj=nxt[nowj]; } } return cnt; }
下面是另一个重要问题,怎么求next数组。
void getnext(char x[],int m,int nxt[]) { int i,j; j=nxt[0]=-1; i=0; while(i<m) { while(-1!=j && x[i]!=x[j])j=nxt[j]; nxt[++i]=++j; } }