串的模式匹配
串的模式匹配即子串的定位操作,一般把要找的子串称为模式串。
最普通的一种算法思想大概如下:
从主串的第pos个字符起和模式串的第一个字符比较;
如果{
相同则指向主串与指向模式串的指针都向后移一位,继续比较;
不同则从主串的下一个字符起和模式串的第一个字符比较;
}
直到指向模式串的指针超出子串的长度,则匹配成功,否则匹配失败。
为了避免语言上的歧义,也给大家一个直观的认识,下面找来几幅图描述了这种算法的整个匹配过程。
第一轮(图1-1):
第二轮(图1-2):
第三轮(图1-3):
第四轮(图1-4)(匹配成功):
这种算法思想直接易懂,在很多情况下也有不错的效率,然而,如果是像主串为“0000000000000000000000001”而模式串为“0000001”时就很浪费时间了,需要很多次的回溯,其时间复杂度最坏能去到O(n*m)。
KMP算法
针对这种情况有一种很著名的算法:KMP算法。其优点在于当匹配出现不同时,主串不是回溯到最开始,然后向后移一位,如图1-1,图1-2描述的那样。而是直接根据部分匹配的结果,尽量使主串指针往右移,其效果就像下面两幅图展示的那样。
第一轮:
第二轮:
假设主串指针为i,模式串指针为j时发生不匹配时,模式串指针需要变为k。如上面那个例子,则第一轮时i为5,j为5,k为2。
那么一般情况下这个k根据什么确定呢?
观察上面的例子可以发现,”T[0]T[1]…T[k-1]”=”S[i-k]S[i-k+1]…S[i-1]”,这是由k本身的意义决定的,因为主串指针为i,模式串指针为j时发生不匹配时,模式串指针需要变为k,即是说模式串0到k-1的字符串必然与主串i-k到i-1的字符串相匹配,且这个k值尽量大。
另外,由于主串指针为i,模式串指针为j时发生不匹配,因此我们也可以知道”T[j-k]T[j-k+1]…T[j-1]”=”S[i-k]S[i-k+1]…S[i-1]”,因为模式串在j之前的部分必然已经与主串相匹配了。
因此我们得到两个k要满足的等式:
”T[0]T[1]…T[k-1]”=”S[i-k]S[i-k+1]…S[i-1]”
”T[j-k]T[j-k+1]…T[j-1]”=”S[i-k]S[i-k+1]…S[i-1]”
还有一些隐含的条件:i>=j>k>0,简化后即j>k>0;k要尽量大。
根据两个等式可以发现其实我们只需要关心模式串内部就可以了,即一个等式:
”T[0]T[1]…T[k-1]”=”T[j-k]T[j-k+1]…T[j-1]”( j>k>0;k要尽量大)
其实从直观上也不难理解,因为主串中对于得到k值有用的信息只是当前主串与模式串已经匹配上的那一段字符串,而这段字符串我们从模式串中同样能得到,因此求得k值可以简化成模式串的内部问题。
另外,如果j=0时,k=-1;
找不到满足条件的k值时,k=0;
令next[j]=k,综合起来就是:
-1(j=0时)
next[j]= max{k|j>k>0且”T[0]T[1]…T[k-1]”=”T[j-k]T[j-k+1]…T[j-1]”}
0(其他情况)
KMP算法思想如下:
从主串的第pos个字符起和模式串的第一个字符比较;
如果{
相同则指向主串与指向模式串的指针都向后移一位,继续比较;
不同则检查next[j] {
如果next[j]=-1则指向主串的指针向后移一位,指向模式串的指针归零,继续比较;
否则主串该字符S[i]和模式串的T[next[j]]字符比较;
}
}
直到指向模式串的指针超出子串的长度,则匹配成功,否则匹配失败。
而如何求得next[j]呢?
首先next[0]=-1。设next[j]=k,即满足max{k|j>k>0且”T[0]T[1]…T[k-1]”=”T[j-k]T[j-k+1]…T[j-1]”},
则next[j+1]是什么呢?
如果T[k]=T[j],则易得max{k+1|j+1>k+1>0且”T[0]T[1]…T[k]”=”T[j-k]T[j-k+1]…T[j]”},即next[j+1]=k+1=next[j]+1。
但如果 T[k]!=T[j]怎么办呢?
其实我们可以把求next[j]的过程也看成一个模式匹配的过程,只不过主串和模式串都是T,而且目的是填上next表。因此T[k]!=T[j]时,我们也可以用KMP算法,使T[j]直接与T[next[k]]进行匹配。
若T[j]= T[next[k]],
则易得max{ next[k]+1|j> next[k]+1>0且”T[0]T[1]…T[next[k]]”=”T[j- next[k]]T[j- next[k]+1]…T[j]”},即next[j+1]=next[k]+1。
若T[j]!= T[next[k]],则同理类推,直到某个k值使T[j]= T[next[k]],若没有满足条件的k值,则next[j+1]=0。
综上,求得next表的算法基本如下:
1 public void getNext(String T,int[] next){ 2 3 int i=0, j=-1; 4 5 next[0]=-1; 6 7 while(i<T.length()-1){ 8 9 if(j==-1||T.charAt(i)==T.charAt(j)){i++;j++;next[i]=j;} 10 11 else{j=next[j];} 12 13 } 14 15 }