KMP算法Java实现以及总结

来源:互联网 发布:上下课铃声软件 编辑:程序博客网 时间:2024/05/21 09:19

原理就不赘述了,直接上代码和测试结果。


/* * 字符串匹配算法 * 输入:目标串s和模式串p * 输出:第一次找到模式字串,返回在目标串的下标位置 */public class Kmp {public static void main(String[] args){String s = "dabcdzaacv";//目标串10个字符String p = "bcd";//模式串3个字符System.out.println("原始字符串匹配结果:" + origin(s,p));System.out.println("改进字符串匹配结果:" + fastfind(s,p));}public static int origin(String target, String pattern){char[] s = target.toCharArray();char[] p = pattern.toCharArray();int pos = 0;for(int i=0,j; i<s.length-p.length+1; ++i){for(j=0; j<p.length; ++j){if(s[i+j]!=p[j])break;}if(j==p.length)return i;}return -1;}public static int fastfind(String target, String pattern){char[] s = target.toCharArray();char[] p = pattern.toCharArray();int[] f = new int[p.length];//失败函数f[j],表示前一次匹配匹配到模式串下标为j的地方,且在j处不匹配,//通过f[j]查找已经匹配成功的部分模式串中,最大的前后匹配的子串,并返回//前子串最后一个字符坐标,指示接下来应该从模式串的哪个地方开始匹配//如模式串匹配成功的部分子串为abcdabcd,则返回第一个abcd的d的下标,为3.failure(p, f);//初始化失败函数f[]int pos = 0;int i=0, j=0;while(i<s.length && j<p.length){if(s[i]==p[j]){//匹配成功则继续++i;++j;}else{if(j==0)//若第一个就匹配失败,则从目标串下一个字符开始匹配++i;elsej = f[j-1]+1;//若不是在匹配模式串第一个字符就失败,则模式串用失败函数回退}}if(j < p.length)return -1;elsereturn i-j;//目标串最后停留位置减去模式串长度即为所求}public static void failure(char[] p, int[] f){f[0] = -1;for(int j=1,i; j<f.length; ++j){i = f[j-1];while(p[j]!=p[i+1] && i>0){i = f[i];}if(p[j] == p[i+1])f[j] = i+1;elsef[j] = -1;}}}

测试结果:


当然我们还会自然地想测试一下速度差距,添加test函数如下(orgin、fastfind、failure函数不再重复):

/* * 字符串匹配算法 * 输入:目标串s和模式串p * 输出:第一次找到模式字串,返回在目标串的下标位置 */public class Kmp {public static void main(String[] args){//目标串76个字符,s1对于改进的算法是几乎最坏情况,因为每次匹配最多匹配成功模式串的第一个字符,不利于失败函数发挥作用。String s1 = "alfjkdlafjkldajfkldakcklvkklvjkclvjekslkfdkldvklcvjkldasjkflabcdefghabcdefgh";//s2对于改进的算法是几乎最好情况,总是匹配到目标串最后再用失败函数回退。String s2 = "abcdefghabcdefgabcdefghabcdefgabcdefghabcdefgabcdefghabcdefgabcdefghabcdefgh";//模式串16个字符String p = "abcdefghabcdefgh";test(s1, s2, p);}public static void test(String s1, String s2, String pattern){long l1 = System.currentTimeMillis();for(int i=0; i<400; ++i){for(int j=0; j<400; ++j){for(int k=0; k<1000; ++k){origin(s1, pattern);}}}long l2 = System.currentTimeMillis();System.out.println("最坏情况下,原始字符串匹配时间:"+(l2-l1)+"毫秒");for(int i=0; i<400; ++i){for(int j=0; j<400; ++j){for(int k=0; k<1000; ++k){fastfind(s1, pattern);}}}l1 = System.currentTimeMillis();System.out.println("最坏情况下,改进字符串匹配时间:"+(l1-l2)+"毫秒");System.out.println();l1 = System.currentTimeMillis();for(int i=0; i<400; ++i){for(int j=0; j<400; ++j){for(int k=0; k<1000; ++k){origin(s2, pattern);}}}l2 = System.currentTimeMillis();System.out.println("最好情况下,原始字符串匹配时间:"+(l2-l1)+"毫秒");for(int i=0; i<400; ++i){for(int j=0; j<400; ++j){for(int k=0; k<1000; ++k){fastfind(s1, pattern);}}}l1 = System.currentTimeMillis();System.out.println("最好情况下,改进字符串匹配时间:"+(l1-l2)+"毫秒");}}


注意:这里的最好最坏指的是模式串在目标串的尾部时(前提),目标串本身对匹配速度的影响。


同时,对改进算法的最好情况是朴素算法的最坏情况,反过来这里所说的最坏情况也是对朴素算法的最好情况。


因为,匹配得越多,对改进算法的失败函数有利 ;匹配得越少,对朴素算法提前跳出每一次循环有利。


测试结果:


从测试结果也可以验证上述的话,最好、最坏是相对朴素算法和改进算法的比较而言。


对于坏情况,改进算法甚至比朴素算法更慢,因为失败函数不仅发挥不了作用,而且初始化失败函数也需要花费时间。


可能有人会问:为什么两种算法在最好情况下花的时间比最坏情况下多呢?

因为最好最坏是针对能不能发挥出改进算法的优势而言。测试的目标串s2比s1更能发挥改进算法的优势。事实上,s1就是比s2更容易找到模式串,这是肯定的。


可能有人会问:为什么最好情况下,改进算法花的时间比朴素算法多,不是更能发挥改进算法的优势嘛?

哈哈哈哈哈,恭喜你问到点子上了!!!(妈的我想了半天,也是为什么我特意把这个测试结果放上来的原因)

很简单,因为改进算法的优势还是比不过失败函数的开销。

那怎么办呢? 改目标串! 使之更能发挥改进算法的优势!!

String s1 = "alfjkdlafjkldajfkldakcklvkklvjkclvjczvzvzcxvzvzvzvczvbvcxbvbzvzvzxvzxbzbzbzcvczvczbczxbzbzbzbzbzxbzbzbzbzbczzzekslkfdkldvklcvjkldasjkflabcdefghabcdefgh";String s2 = "abcdefghabcdefgabcdefghabcdefgabcdefghabcdefgabcdefghabcdefgabcdefghabcdefgabcdefghabcdefgabcdefghabcdefgabcdefghabcdefgabcdefghabcdefgabcdefghabcdefgh";

测试结果吓了我一跳,没想到一下子改进算法快了这么多。



做到这我还有一个问题:感觉对于好情况下,改进算法相比于朴素算法快的没有那么明显啊。

所以我想探究一下,改进算法的失败函数初始化大概花了多少时间。

修改如下:

添加一行代码:

//对于测试模式串的失败函数public static int[] f = new int[]{-1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7};


注释掉两行代码:

//int[] f = new int[p.length];


//failure(p, f);//初始化失败函数f[]


测试结果(使用的是刚刚改进后的目标串):

通过对比可以知道,改进算法的失败函数的花销确实很大。

还有一点希望注意的是,最坏情况下的改进算法,在去掉初始化失败函数的时间后,终于比朴素算法快了。




总结:

1.KMP算法很牛逼。

2.KMP算法真正实现时,由于初始化失败函数也需要时间,所以,不一定比朴素算法快。(我把模式串放在目标串的尾部也是这个原因,如果在头部,那大家都是第一次查找就找到了,失败函数也就没有必要了)



测试结果均多次测试,如有错误,恳请指出。


原创粉丝点击