public static int worstMatch(String s, String p)
{
int sLen = s.length();
int pLen = p.length();
int i = 0;
int j = 0;
while (i < sLen && j < pLen)
{
if (s.charAt(i) == p.charAt(j))
{
//如果当前字符匹配成功(即S[i] == P[j]),则i++,j++
i++;
j++;
}
else
{
//如果失配(即S[i]! = P[j]),s回退j步到匹配开始的位置,
//然后从下一位置重新开始,即i = i - j+1
i = i - j + 1;
j = 0;
}
}
//匹配成功,返回模式串p在文本串s中的位置,否则返回-1
if (j == pLen) return i - j;
return -1;
}
对于KMP算法,这篇博客讲解得非常透彻。KMP算法详解
总的思想:记录模式串中最长重复子串,只移动模式串,当发现不匹配字符时,模式串的指针尽可能少的回退到下一个可能匹配的位置。
例如匹配串s是ABCDEABCDEABCDFABCDE,模式串p是ABCDEABCDF,那么模式串p中F之前实际上有最长重复子串ABCD,所以当指针i移到s[9],指针j移到p[9]时,发现字符E和字符F不相等,则i不变j回退到4,下次匹配时直接比较s[9]和p[4]即可,而无须i回到1,j回到0。即当s[i]和s[j]不匹配时,j=next[9]=4。
那么如何得到next数组呢,假设已知next[i]=k,则p[0]、p[1]、p[2]、…、P[k-1]这前k个字符与p[i-k]、p[i-k+1]、p[i-k+2]、~、p[i-1]这后k个字符相同,那么计算next[i+1]时,我们只需要判断p[i]和p[k]两个字符就有可能确定next[i+1]的值。如果p[i]=p[k],依照next的定义即可知道next[i+1]=k+1。
以ABEABCDABEABCD为例,我们知道next[12]=5,那么在求next[13]时,p[5]=C、p[12]=C、它们相等那么就可以直接确定next[13]=6。
再看p[i]和p[k]不相等的情况,我们假设next[k]=j,那么第一段p[0]、…、p[j-1],第二段p[k-j]、…、p[k-1],第三段p[i-k]、…、p[i-k+j-1]以及第四段p[i-j]、…、p[i-1]这四段长度为j的字符串都是相同的。也就是说如果p[j]==p[i],那么p[0]、…、p[j]这前j+1个字符串与p[i-j]~p[i]这后j+1个字符串一定是相同的,也就是说next[i+1]=next[k]+1。
以ABEABCDABEABED为例,我们知道next[12]=5,但是p[5]是C、p[12]是E,它们不相等所以不能得出next[13]=6,再看next[5]的值,它前面是ABEAB,所以next[5]=2,我们比较p[2]和p[12]的值发现它们相等,这个时候我们就可以得出next[13]=3。而若以ABEABCDABEABDC为例,因为next[12]=5,next[5]=2,但是此时p[2]!=p[12]且next[2]=0,则可以直接得出next[13]=0。
从上面的分析可以看出,在已知next[i]=k时,若p[i]=p[k],则next[i+1]=k+1。若p[i]!=p[k],则需要判断next[next[i]]的值,即若next[next[i]]=j,则判断一下p[j]和p[i]的值,若相等则next[i+1]=next[next[i]]+1。若不相等则需要继续判断直至next值为0。从编程的角度来看,我们判断p[i]的值是否能追加到之前的最长相同前后缀时,求的其实是next[i+1],也就是最外层循环只有n-1次,同时有可能存在一个内部循环。于是可以写出以下代码:
private static int[] getNext(String pattern){
if(pattern==null) return null;
int len=pattern.length();
if(len==0) return null;
int[] next=new int[len];
next[0]=-1;
int k;
for(int i=0;i0){
if(pattern.charAt(i)==pattern.charAt(k)){
next[i+1]=k+1;
break;
}else{
k=next[k];
}
if(k<=0) next[i+1]=0;
}
}
}
return next;
}
不过这段代码虽然逻辑上比较清晰但是不够简练,它可以优化成下面这段代码:
private static int[] getNext(String pattern){
if(pattern==null) return null;
int len=pattern.length();
if(len==0) return null;
int[] next=new int[len];
next[0]=-1;
int k=-1;
int i=0;
while(i
那么完整的KMP代码就是:
public static int kmpSearch(String s, String p)
{
if(s==null) return -1;
if(p==null) return -2;
int sLen=s.length(),pLen=p.length();
if(sLen==0) return -1;
if(pLen==0) return -2;
int[] next=getNext(p);
int i=0,j=0;
while (i < sLen && j < pLen)
{
//①如果j = -1,或者当前字符匹配成功(即S[i] == P[j]),都令i++,j++
if (j == -1 || s.charAt(i) == p.charAt(j))
{
i++;
j++;
}
else
{
//②如果j != -1,且当前字符匹配失败(即S[i] != P[j]),则令 i 不变,j = next[j]
//next[j]即为j所对应的next值
j = next[j];
}
}
if (j == pLen)
return i - j;
else
return -1;
}
同时大神的博客也讲到了next数组的一个优化,假设匹配串ababbcabadabc、模式串ababbcabab。next数组不优化时为[-1,0,0,1,2,0,0,0,2,3],第一趟匹配失败发生在i=9,j=9。此时next[9]=3,所以第二趟匹配开始时i=9,j=3,而这必然是匹配失败的,因为s[9]=d,p[3]=b之前已经比较了d和b不相等,所以s[9]和p[3]的匹配完全是没必要的。同样next[3]=1,s[1]=b,也无需匹配。这种优化是很有必要的,因为next数组优化是一次性的,而模式串中有可能多次出现类似情况,如果在匹配时再去尝试这种必定失败的操作,会造成很大的不必要的性能开销。
所以当匹配失败且p[next[j]]=p[j]时,需要循环回退到不与p[j]相同的next点。那么代码中需要再增加一个循环判断吗?实际上也并不需要,因为在求next数组时,是从前向后计算的,我们可以保证之前的每个p[next[k]]不等于p[k],那么如果某点i的前继为k且p[i]=p[k],则next[i]应为k的前继next[k],那么p[next[i]]=p[next[k]]不等于p[k]即不等于p[i]。
因此代码中实际上只须回退一次就够了,没必要循环回退,改进的代码如下
private static int[] getNext(String pattern){
if(pattern==null) return null;
int len=pattern.length();
if(len==0) return null;
int[] next=new int[len];
next[0]=-1;
int k=-1;
int i=0;
while(i
BM算法的核心思想是后向匹配,坏字符和好后缀规则。
三步引入BM算法
BM算法详解
KMP算法中,都是从匹配串S和模式串P的起始位置开始匹配,模式串P的指针是从左向右移动。而BM算法是后向匹配,模式串P的指针是从右向左移动。当然匹配串S的指针肯定仍然是从左向右移动。
实际上对于长度为n的匹配串S和长度为m的模式串P,即使是用暴力匹配法也最多只需要匹配n-m+1次,每次需要比较两个长度为m的字符串是否匹配,发现当前子串不匹配时将匹配串的起始指针整体右移一位,即进行另一次匹配。而BM算法的目标就是尽量增大这个步长,每次匹配失败之后尽可能移动多位。
public static int reverseMatch(String s, String p)
{
int sLen = s.length();
int pLen = p.length();
int iStart = pLen-1;
int i=iStart;
int j = pLen-1;
while (j>=0&&iStart<=sLen-1)
{
if (s.charAt(i) == p.charAt(j)) {
//如果当前字符匹配成功,则指针从后往左移动一位
i--;
j--;
} else {
//如果存在不同字符,则开始下一次匹配,iStart只移动了一位
iStart = iStart+1;
i=iStart;
j = pLen-1;
}
}
//匹配成功,返回模式串p在文本串s中的位置,否则返回-1
if (j == -1) return iStart - pLen+1;
return -1;
}
【待完成】
【待完成】
【待完成】Sunday算法介绍
【待完成】基于Hash思想的RK算法