KMP算法

来源:互联网 发布:python的前景 编辑:程序博客网 时间:2024/06/08 06:34

注:本文主要是按照李春葆版本《数据结构》来总结的。

1. BF算法(简单匹配算法) 时间复杂度O(n*m)

public int index(char [] s,char [] t){if(s==null || t==null ||s.length<t.length){return -1;}for(int i=0;i<=s.length-t.length;i++){int j=i,k=0;for(;k<t.length && s[j]==t[k] ;j++,k++);if(k==t.length){return i;}}return -1;}

或者

public int index2(char[] s,char[] t){if(s==null || t==null ||s.length<t.length){return -1;}int i=0,j=0,k=-1; //k用来返回最后的下标while(i<s.length && j<t.length){if(s[i]==t[j]){i++;j++;}else{i=i-j+1;j=0;}}if(j>=t.length){return k=i-t.length;}return k;}

2. KMP 算法 时间复杂度O(n+m)

KMP算法消除了主串指针的回溯,利用模式串当前子串的前缀和后缀匹配的子串的最长长度来计算。

前缀:长度为n的字符串中按排列顺序的1或n-1个字符组成的子串,如abcab的前缀是a, ab, abc, abca

后缀:和前缀类似,由字符串后面的字符组成,abcab的后缀是b, ab, cab, bcab

若模式串 t 和主串 s 中符合


即"t0t1…tj-1"="si-jsi-j+1…si-1"  如果在模式t中,"t0t1…tj-1"≠"t1t2…tj",则"t0t1…tj-1"≠"si-j+1si-j+2…si",对于“失配”现象,我们需要找到一个前缀,使得"t0t1…tk-2"≠"tj-k+1tj-k+2…tj-1" 且 "t0t1…tk-1"="tj-ktj-k+1…tj-1“才有"tj-ktj-k+1…tj-1"="si-ksi-k+1…si-1"="t0t1…tk-1"


这样当si与tj 比较失配时,可以直接将si与tk比较,也就是模式串t右滑j-k位,k即是next[j].由于t0t1...tj中可能存在多个前缀与后缀相同的情况,我们应该选择最长的前缀进行滑动,保证回溯最少。

public int [] GetNext(char[] t){if(t==null || t.length==0){return null;}//k表示最长相同前后缀的长度,next[0]=-1是规定的,k=0表示相同的前后缀长度为0int j=0,k=-1;//k<j,且-1<=k<=j-1//next[]用来存放已经计算好的最长前后缀长度int [] next=new int[t.length];next[0]=-1;while(j<t.length-1){ //前缀长度至少比模式串长度少1if(k==-1 || t[j]==t[k]){ //k=-1表示没有相同的前缀和后缀,t[j]=t[k]表示当前的字符也相同,k++;  //前后缀长度加1j++; //如果当前字符相同,说明包含当前字符为前缀的子串的next增加1,所以j需要加1next[j]=k;}else{k=next[k];  //当前字符不相同,那么最长的前后缀长度就是前面得到的最大长度}}return next;}
public int KMP(char[] s,char[] t){if(s==null ||t==null ||s.length<t.length){return -1;}int [] next=GetNext(t); //得到模式串t的next数组int i=0,j=0,k=-1; //k用来返回最后的下标//i表示主串中各个字符的下标,所以初始值为0,j初始值为0,表示模式串与主串先顺序比较,如果发现不匹配,则模式串进行滑动while(i<s.length && j<t.length){if(j==-1 || s[i]==t[j]){ //注意:j=next[j],所以j可能等于-1i++;j++;}else{ //此时,主串s不用回溯,只用移动模式串t即可j=next[j];}}if(j>=t.length){k= i-t.length;}return k;}

3. KMP改进算法
例如主串s为 aaabaaab, 模式串t为 aaaab,模式串的next数组是 -1 0 1 2 3,匹配如下:

由于t 的前4个字符相同,所以当i=3, j=3 失配时,也就是a 不等于b ,应该不用比较 j=0,1,2, 应该直接比较 i=4, j=0 。
next[j]=k, 如果在模式串中 tj=tk, 而此时已经失配( si 不等于 tj), 则 si 一定不会等于 tk,所以此时将模式串向后移至 next[j] 也一定会失配。此时,应该将 si 直接和 t next[k] 比较,也就是说,next[j] 应该和 next[k] 相等(tj=tk 时)

为此将next[j] 修正为 nextval[j]:
比较t.data[j] 和 t.data[k],若不等,则 nextval[j]=next[j]=k; 若相等nextval[j]=nextval[k];

public int[] GetNextval(char[] t){if(t==null ||t.length==0){return null;}int [] nextval=new int[t.length];nextval[0]=-1;int j=0,k=-1;while(j<t.length-1){if(k==-1 || t[j]==t[k]){k++;j++;if(t[j]!=t[k]){ //如果不相等,则nextval[j]就是next[j],也就是knextval[j]=k;}else{ nextval[j]=nextval[k];}}else{k=nextval[k];}}return nextval;}
public int improvedKMP(char[]s ,char[] t){if(s==null ||t==null ||s.length<t.length){return -1;}int [] nextval=GetNextval(t); //得到模式串t的nextval数组int i=0,j=0,k=-1; //k用来返回最后的下标//i表示主串中各个字符的下标,所以初始值为0,j初始值为0,表示模式串与主串先顺序比较,如果发现不匹配,则模式串进行滑动while(i<s.length && j<t.length){if(j==-1 || s[i]==t[j]){ //注意:j=nextval[j],所以j可能等于-1i++;j++;}else{ //此时,主串s不用回溯,只用移动模式串t即可,且按照nextval来移动j=nextval[j];}}if(j>=t.length){k= i-t.length;}return k;}
improvedKMP() 和KMP() 的不同就在于匹配失效后,模式串t 的移动由 next[j] 变成了nextval[j]; 重点主要是next[] 和 nextval[] 的求法,而nextval[j] 相对于next[] ,主要是看 tj 是否等于tnext[j](也就是t[k])。


原创粉丝点击