KMP

来源:互联网 发布:linux kasan 编辑:程序博客网 时间:2024/06/05 11:14

  KMP是个字符串匹配的算法。

  如果想知道一个字符串在另一个字符串中出现没有或者出现几次,最暴力的方法是一个一个比较。假设有两个串ababcabcacbab和abcac,i和j分别表示第一个串和第二个串匹配到的位置,普通方法是i=0,j=0,a[i]=b[j], i++,j++,i=1,j=1,i++,j++,i=2,j=2,此时a[i]!=b[j],接下来i=1,j=0.再开始比较。。

  这种方法有些步骤其实是浪费的,因为i=1和j=0的时候已经间接的比较过了(因为i=1和j=1是匹配的,只要知道b[0]和b[1]是否相等就知道a[1]和b[1]的匹配情况了)。KMP就是这么一个算法,可以使i不后退,失配时只要使模式串移动一定距离继续匹配。假设用next[]数组保存主串下标为i的位置失配时应该和模式串下标为j的位置匹配,next[0]=-1,因为第一个都不匹配i就只能+1了。a是主串,b是模式串,L1是a的长,L2是b的长,由此得到代码:

void KMP(){int i=0,j=0;while(i<L1&&j<L2){    if(j==-1||a[i]==b[j]){i++;j++;}    else j=next[j];    if(j==L2)cnt++;}}

  那next数组怎么获得呢?假设a[i]和b[j]失配,因为j之前的字符都是匹配的,所以如果j的前k个和倒数k个是一样的(k取最大值),失配时就可以把a[i]和b[k]比了,从b[0]到b[k-1]已经匹配了。可以根据此写出:

void get_next(){int i=0,j=-1;next[0]=-1;while(i<L2){if(j==-1||b[i]==b[j]){i++;j++;next[i]=j;}else j=next[j];}}

  其中b[0]到b[j-1]和b[i-j]到b[i-1]是已经匹配的,那么如果b[i]=b[j],则next[i+1]=j+1(如果和b[i+1]失配就再和b[j+1]比较),否则j=next[j],(假设abcabbabcabc,abcab都已经匹配,此时比较b[5]和b[11],不匹配,因为next[5]=2,所以该比较b[2]和b[11],发现匹配。因为本来b[0]到b[4]等于b[6]到b[10],next[5]=2说明b[0]=b[3],b[1]=b[4],又b[3]=b[9],b[4]=b[10],所以b[0]=b[9],b[1]=b[10],直接比较b[2]和b[11]就行了),如果j=-1了说明前面没一个能匹配上b[0]的,也就是重新开始和b[0]匹配吧。。。

  但是发现一个问题,如果i++,j++之后b[i]=b[j],next[i]还等于j的话b[i]失配了b[j]照样失配,所以应该有个判断,因此最好是:

void get_next(){int i=0,j=-1;next[0]=-1;while(i<L2){if(j==-1||b[i]==b[j]){    if(b[++i]!=b[++j])next[i]=j;    else next[i]=next[j];}else j=next[j];}}

  其实next[L2]仍是有值的,比如aba这个字符串,得到next[0]到next[3]的值为-1,0,-1,1,next[3]的值就是整个串前后重复的个数,用这个可以解决找一个串在另一个串出现的次数,重复也包括。比如有

AZAAZAZAZA
前3个都匹配上了,出现1次,这时j等于3了,第一个串肯定是要往后移动的,正好利用next[3]=1(a[3]=0,当作b[3]和a[3]失配),所以直接比较b[3]和a[1]。。

  总之,KMP算法的本质就是主串的i值不后退,根据next[]避免重复比较,最后得到主串每个位置能和模式串匹配多少。

 

原创粉丝点击