今天突然被问起字符串匹配的算法,竟哑口无言,只剩下一些模糊的印象,回来恶补了一番。
朴素的匹配算法很简单,往前找如果有部分成功的也回退,如果目标字符串长度为m,源字符串长度为n则时间复杂度为O(m*n);
而克努斯等人发现了更快的不用回退的算法,即KMP(Knuth,Morris,Pratt)算法,在部分查找成功之后,用成功的记录确定下次匹配开始位置。时间复杂度为O(m+n);
KMP算法在目标字符串及源字符串较小的情况下运行时间和朴素匹配的时间复杂度相差不大,但一旦查找很长的字符串并且 目标字符串首尾相同的子字符串很多。
那么KMP的优势一下就体现出来了,它是跳跃式前进,比O(m*n)快了几个数量级。
public class KMPMatch {
/*KMP算法是朴素模式匹配算法(复杂度为O(m*n))的改进
* 在查找过程中不会退,运用失败函数提供重新匹配起始位置
* 时间复杂度为O(m+n)
* 是Knuth,Morris,Pratt一起提出的算法,故此命名
*/
/*这里为了性能,不用Java封装类型,一般需要匹配的字符不至于超过100,如果有需求
*的话可以换种写法,使用构造函数来给数组指定大小
*/
private int [] failArray = new int [100];
/*
* 如 public KPMMatch(int size){
* failArray = new int [size];
* }
*
* */
public int match(char [] pattern,char[] source ){
//定义2个变量存储目标字符串长度和待查找字符串长度
int pLength = pattern.length;
int sLength = source.length;
//定义2个变量用于表示2个字符串下标
int pi = 0,si = 0;
//在查找未结束之前
while(pi< pLength && si <sLength){
//如果相等,一直往前
if(pattern[pi]==source[si]){
++si;
++pi;
}//如果第一次就失败
else if(0 == pi){
++si;
}//这里使用失败数组确定重新匹配的起始位置
else{
pi = failArray[pi-1]+1;
}
}
//如果知道源字符串到结尾仍未找到,那么失败,返回-1
if(pi < pLength)
return -1;
//否则查找成功
return si - pLength;
}
public int fail(char [] pattern ){
int pLength = pattern.length;
failArray[0] = -1;
//对所有进行计算,从第二位开始
for(int j = 1; j < pLength - 1;j++){
//开始时,pi为-1
int pi = failArray[j-1];
//如果p0p1p2 == p6p7p8 但是 p3 != p9,那么失败
while(pattern[j]!=pattern[pi+1] && pi > 0){
pi = failArray[j-1];
}
//如果成功,值加1
if(pattern[j]==pattern[pi+1])
failArray[j] = pi+1;
else
failArray[j] = -1;
}
return 0 ;
}
}
public class Test {
public static void main(String[] args) {
KMPMatch kmpTest = new KMPMatch();
char [] p = {'c','a','b'};
char [] s = {'c','c','a','e','f','h','b','<span style="color:#FF0000;">c','a','b'</span>,'c'};
kmpTest.fail(p);
System.out.println(kmpTest.match(p, s));
}
}
额,运行的时候,电脑内存突然爆炸,而且一直在运行,那就说明程序陷入了死循环,用System.out.println("hello");来逐步调试,才发现
竟然没调用fail方法,而failArray中全是0,然后match方法一直走第三个判断语句,source不向前,pattern也不向前,所以就一直在while循环里,死掉了。。。