一段正在被编辑的文本构成一个文件,而所要搜寻的模式是用户正在输入的特定的关键词,有效解决这个问题的算法叫做字符串匹配算法。假设给定文本是长度为n的数组T,而模式是一个长度为m的数组P。算法导论第32章,字符串匹配问题。本章一共给出了四个算法,分别是朴素算法,Rabin-Karp算法,有限自动机算法和kmp算法。
一、朴素算法
朴素算法就是直接的暴力破解,两层循环,外层n-m+1,内层m,所以时间复杂度为O((n-m+1)*m)。代码如下
public static List<Integer> naiveStringMatcher(String T, String P){
List<Integer> re=new ArrayList<Integer>();
int n=T.length();
int m=P.length();
for(int i=0; i<=n-m; i++){
int j;
for(j=0; j<m; j++){
if(P.charAt(j)!=T.charAt(i+j)){
break;
}
}
if(j==m){
re.add(i);
}
}
return re;
}
程序也比较简单,不多介绍了。
二、Rabin-Karp算法
个人理解Rabin-Karp算法是朴素算法的改进,它在最坏情况下运行情况下与朴素算法相同即O((n-m+1)*m),从代码也可以看出,两个算法的外层是完全一样的,都是n-m+1,优化之处在于内层,朴素算法是从0到m逐个比较,所以复杂度是m,但是Rabin-Karp算法通过一个哈希函数,将m长度的字符串进行映射,通过O(1)时间就能求出,所以算法的过程就是预处理时求出P的哈希值p,然后在外层循环中逐个求内层m个字符串的哈希值t,比较二者是否相同,如果哈希值相同,再进一步判断两个字符串是否完全相同,所以只要哈希函数设计的好,算法的运行时间可以达到O(n+m)。
public static List<Integer> rabinKarpMatcher(String T, String P){
List<Integer> re=new ArrayList<Integer>();
int n=T.length(), m=P.length();
int q=13;//直接取素数13
int d=10, h=1;//d直接去10
int p=0, t=0;
for(int i=0; i<m-1; i++){//根据P的长度求出h,即具有m数位的文本窗口的高位数位上的数字“1”的值
h=(h*d)%q;
}
for(int i=0; i<m; i++){//预处理,初始化求出p和t0的值
p=(d*p+P.charAt(i)-'0')%q;
t=(d*t+T.charAt(i)-'0')%q;
}
for(int i=0; i<=n-m; i++){//无论何时执行,都有根据t(i)求t(i+1)
if(p==t){//如果p与t(i)相等,说明取模相等,但有可能是伪命中点
int j;
for(j=0; j<m; j++){//进一步判断
if(P.charAt(j)!=T.charAt(i+j)){
break;
}
}
if(j==m){//如果完全一样,则加入答案中
re.add(i);
}
}
if(i<(n-m)){//根据t(i)求t(i+1)
t=(d*(t-(T.charAt(i)-'0')*h)+T.charAt(i+m)-'0')%q;
if(t<0){
t+=q;
}
}
}
return re;
}
三、有限自动机
四、KMP算法
KMP应该是字符串匹配问题最常见的算法了,反正我是最先接触到这个算法,后来才在算法导论上看到有专门的一章介绍。这个算法的匹配时间为O(n),无需像有限自动机一样计算转移矩阵,只用到一个辅助数组Pi,它在O(m)时间内根据P计算出来。
其实匹配函数和预处理函数的大体相同,都是一个字符串针对模式P的匹配,只不过预处理过程是模式P本身针对自己的匹配。所以程序虽然长,但是只要理解了过程,也可以写出来,需要注意的就是与书上不同点在于,书上的数组都是从1开始,而程序中都是从0开始的,所以程序中需要作出调整,我在自己实现的时候把k的取值从-1开始,然后在后面再相印的加一。
private static int[] getNext(String P){
int m=P.length();
int[] next=new int[m+1];
next[0]=next[1]=0;
int j=0;
for(int i=1; i<m; i++){
while(j>0&&P.charAt(i)!=P.charAt(j)){
j=next[j];
}
if(P.charAt(i)==P.charAt(j)){
j++;
}
next[i+1]=j;
}
return next;
}
//KMP算法 匹配过程 O(n)
public static List<Integer> kmpMatcher(String T, String P){
List<Integer> re=new ArrayList<Integer>();
int[] next=getNext(P);
int m=P.length();
System.out.println(Arrays.toString(next));
int j=0;
for(int i=0; i<T.length(); i++){
while(j>0&&T.charAt(i)!=P.charAt(j)){
j=next[j];
}
if(T.charAt(i)==P.charAt(j)){
j++;
}
if(j==m){
re.add(i-j+1);
j=next[j];
}
}
return re;
}
五、总结
以上是根据算法导论书上的内容结合自己的理解,代码全部是自己写的。