KMP算法详解
一直以来对KMP算法理解的不是很透彻。看了左神的程序员代码面试指南后感觉基本明白了。
我们都知道KMP算法最重要的是next数组,next[i]的含义是在match[i]之前的字符串match[0..i-1]中,必须以match[i-1]结尾的后缀子串(不能包含match[0])与必须以match[0]开头的前缀子串(不能包含match[i-1])最大匹配长度是多少。比如match="aaaab",我们来求一下next[4],match[4]=='b',它之前的字符串为"aaaa",根据定义它的后缀子串和前缀子串的最大匹配为"aaa",所以next[4]=3.
假设从str[i]出发,匹配到j位置时发现与match中的字符不一致,此时,我们利用next数组,next[j-i]表示match[0..j-i-1]这一段字符串前缀与后缀的最长匹配。假设前缀是a区域这一段,后缀是b区域这一段,再假设a区域的下一个字符是match[k],如图所示。
那么下次匹配时直接让str[j]与match[k]进行匹配。相当于match向右滑动。如果match滑到最后也没匹配出来,就说明str中没有match,为什么这样做是正确的呢?
如图,匹配到A字符和B字符才发生的不匹配,所以c区域等于b区域,有next数组可知b区域与a区域相等,所以直接把字符C滑到A的位置开始匹配即可,那么为什么中间这一段是不用检查的呢?
假设d区域开始的字符是不用检查区域的一个位置,如果从这个位置开始能够匹配出match,那么显然d区域和e区域相等,假设d区域对应到match中是d'区域,也就是字符B之前字符串的后缀,而e是match的前缀,随意对match来说,相当于找到了B字符之前字符串的一个比a,b区域更的大前缀与后缀匹配长度,这与next数组的值是自相矛盾的,因为next数组保存的就是最大前缀与后缀匹配长度,所以如果next数组的值计算正确,这种情况绝对不会发生。
匹配过程代码
int getIndexOf(string& s,string& m){ if(s.length()<m.length()) return -1; if(s.empty()||m.empty()) return 0; int si=0,mi=0; vector<int> next=getNext(m); while(si<s.length()&&mi<m.length()){ if(s[si]==m[mi]){ si++; mi++; }else if(next[mi]==-1){ si++; }else{ mi=next[mi]; } } return mi==m.length() ? si - mi : -1; }next数组的求解
对match[0]来说,在它之前没有字符,说以next[0]规定为-1。对match[1]来说,它只用长度为0的前后缀,所以next[1]为0。之后对于match[i](i>1)来说:
1.因为是从左到右求解next数组,所以在求next[i]的值时,前面的next值均已知。假设match[i]字符为A,match[i-1]为B,通过next[i-1]可以知道B字符前的最大前缀后缀匹配长度,分别为/和k区域。然后看字符C与B是否相等。
2.如果C与B相等,那么next[i]=next[i-1]+1
3.如果C与B不相等,就看字符C之前的前缀后缀匹配情况,假设C是第cn个字符,那么next[cn]就是其最长前缀后缀匹配长度。
如图,m和n区域为字符C之前字符串的最长匹配的前缀后缀区域,m'区域为k区域最右且长度与m区域相同,因为k区域和/区域是相等的,所以m和m'区域也相等,字符D为n区域后的一个字符,接下来比较字符D与B是否相等。
1)如果相等,那么next[i]=next[cn]+1。
2)如果不等,继续往前跳到字符D,之后的过程与跳到字符C类似,一直进行这样跳的过程,只要有相等的情况,next[i]的值就能确定。
4.如果跳到match[0]的位置,说明字符A之前的字符串不存在前缀后缀匹配的情况,则令next[i]=0。
求解next数组代码
vector<int> getNext(string& ms){ vector<int> next(ms.length()); if(ms.length()==1){ next[0]=-1; return next; } next[0]=-1; next[1]=0; int pos=2; int cn=0; while(pos<next.size()){ if(ms[pos-1]==ms[cn]) next[pos++]=++cn; else if(cn>0) cn=next[cn]; else next[pos++]=0; } for(int i=0;i<next.size();i++) cout<<next[i]; cout<<endl; return next; }