注:本文主要是按照李春葆版本《数据结构》来总结的。
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表示相同的前后缀长度为0
int 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){ //前缀长度至少比模式串长度少1
if(k==-1 || t[j]==t[k]){ //k=-1表示没有相同的前缀和后缀,t[j]=t[k]表示当前的字符也相同,
k++; //前后缀长度加1
j++; //如果当前字符相同,说明包含当前字符为前缀的子串的next增加1,所以j需要加1
next[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可能等于-1
i++;
j++;
}else{ //此时,主串s不用回溯,只用移动模式串t即可
j=next[j];
}
}
if(j>=t.length){
k= i-t.length;
}
return k;
}
由于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],也就是k
nextval[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可能等于-1
i++;
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])。