当前位置:   article > 正文

java实现kmp算法_java计算kmp

java计算kmp

简介引言和问题引入

kmp 算法:Knuth-Morris-Pratt 字符串查找算法,常用于在一个文本串S内查找一个模式串P 的出现位置,这个算法由Donald Knuth、Vaughan Pratt、James H. Morris三人于1977年联合发表,故取这3人的姓氏命名此算法。下文中统一称去查找的字符串为文本串,被查找的字符串为模式串。

kmp算法是一个比较难比较抽象的算法,网上对它的解释说明也很多,但是关于怎么找到next前缀表,为什么要用这个前缀表,为什么前缀表有用,纯理论即使是配了图加以说明,可能还是令人不是很理解。现在我想用我看见这个kmp算法代码的时候,想的思路分析一下,也许会有代入感,更好理解。

由于本人水平有限,仅阐述一下自己的一些想法,难免表达有误或者论述不严谨,欢迎纠错,感谢您的阅读。

常规的解决方案

  1. public int strStr(String haystack, String needle) {
  2. int m = needle.length();
  3. // 当 needle 是空字符串时我们应当返回 0
  4. if (m == 0) {
  5. return 0;
  6. }
  7. int n = haystack.length();
  8. if (n < m) {
  9. return -1;
  10. }
  11. int i = 0;
  12. int j = 0;
  13. while (i < n - m + 1) {
  14. // 找到首字母相等
  15. while (i < n && haystack.charAt(i) != needle.charAt(j)) {
  16. i++;
  17. }
  18. if (i == n) {// 没有首字母相等的
  19. return -1;
  20. }
  21. // 遍历后续字符,判断是否相等
  22. i++;
  23. j++; //i控制长字符串,j控制要匹配的字符串,若未匹配成功,则i,j分别回到开始匹配的地方
  24. while (i < n && j < m && haystack.charAt(i) == needle.charAt(j)) {
  25. i++;
  26. j++;
  27. }
  28. if (j == m) {// 找到
  29. return i - j;
  30. } else {// 未找到
  31. i -= j - 1;
  32. j = 0;
  33. }
  34. }
  35. return -1;
  36. }

在常规的思路中,i在文本串中检索,j在模式串中检索,当i,j对应位置的字符相等,开始一次匹配尝试,若不成功 i -= j-1   i要回到开始匹配的后一个位置,这会导致i在进入匹配尝试后进行的一些比对(在跳出匹配模式前)失去意义。所以希望产生一个算法,可以使i一直“前进”,而不用存在这个回溯的操作。

KMP

代码

我们直接看代码,一步一步分析

  1. public void getNext(int[] next, String s){ //getNext_1(next, needle)
  2. int j = -1;
  3. next[0] = j;
  4. for (int i = 1; i < s.length(); i++){
  5. while(j >= 0 && s.charAt(i) != s.charAt(j+1)){
  6. j=next[j];
  7. }
  8. if(s.charAt(i) == s.charAt(j+1)){
  9. j++;
  10. }
  11. next[i] = j;
  12. }
  13. }
  14. public int strStr(String haystack, String needle) {
  15. if(needle.length()==0){
  16. return 0;
  17. }
  18. int[] next = new int[needle.length()];
  19. getNext(next, needle); //初始化getNext数组
  20. int j = -1;
  21. for(int i = 0; i < haystack.length(); i++){
  22. while(j>=0 && haystack.charAt(i) != needle.charAt(j+1)){
  23. j = next[j];
  24. }
  25. if(haystack.charAt(i) == needle.charAt(j+1)){
  26. j++;
  27. }
  28. if(j == needle.length()-1){
  29. return (i-needle.length()+1);
  30. }
  31. }
  32. return -1;
  33. }

分析主函数

我们通过观察主函数,注意到先是做了一个极端情况的判断,再是初始化了一个next数组,最后对文本串做了一次遍历。观察for循环,一看见会对while循环感到不理解,那就先假设永远跳不进while循环,观察一下运行逻辑:

假设 文本串abcS  模式串abc 

很明显,进入不到while循环,在做了三次判断文本串和模式串的if语句后,给出了返回值。

从这边可以得出:

在可以匹配得到的情况下,不管前面发生了什么操作,这个for循环最后的几步,一定是i在文本串中开始,伴随j在模式串中变化,直到完成匹配,最后的i在文本串要匹配的末尾。

对于i的变化,逻辑非常清晰,就是for循环中,每次结束,都+1

但是j的变化,通过j = next[j]实现,它的变化一开始不明白。

然而j要发生变化,有两个条件 :

1 - j>=0对应是已经进入了匹配模式,因为直到第二个if成立第一次,才会使j>=0

2 - 是在i j+1 位置对应文本串,匹配串对应的字符不同,即要退出,也要注意 j 代表的是模式串中正在匹配的字符的前一个位置。在匹配失败后,i不变,所以j肯定要做一些非常规变化。

kmp在解决哪一类情况

举一个简单的例子

文本串abababc   模式串ababc

按照传统的匹配方式,i j从0出发,当其值为4时,跳出匹配,i变为1,j变为0

但是可以发现 模式串ababc完全没有从文本串第二个b开始匹配的必要,如果可以直接从第二个a开始匹配那么就一步到位了,但是按照kmp的说法,i不变了,已经在4的位置,那么如果在这一次匹配失败的操作后,及在一次j = next[j]后,如果j可以去2的位置,那么i ,j继续向后遍历,也可以达到一样的效果。这是因为 模式串中两个ab通过了匹配,重复了,把模式串中第一个ab的位置移到文本串中第二个ab位置的开始匹配,就可以更加便捷。

前缀表

那么使得上述的j去到该去位置的next数组就称为前缀表,前缀表的作用就是在匹配失败的时候,

让j跳到之前已经匹配过的地方。

前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串

后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串

前缀表记录的是 在 模式串到j的之前的字符串中最长相等前后缀

即用前缀表做了一次j的跳转后,会到最长相等前缀末尾的位置

前缀表怎么工作的

说明 图中 [ ]表示一些重复文本(即为模式串的相同前后缀)xxx 代表不匹配的字符,...代表一些普通字符 

我将j 指针的变化,转变为上下字符串 位置的对应,上下位置相对的,代表上下完成或正在进行匹配。

如何得到前缀表

  1. public void getNext_1(int[] next, String s){ //getNext_1(next, needle)
  2. int j = -1;
  3. next[0] = j;
  4. for (int i = 1; i < s.length(); i++){
  5. while(j >= 0 && s.charAt(i) != s.charAt(j+1)){
  6. j=next[j];
  7. }
  8. if(s.charAt(i) == s.charAt(j+1)){
  9. j++;
  10. }
  11. next[i] = j;
  12. }
  13. }

在这个写法中,第一个元素无法回溯,next[0] 为-1   

当if 被触发一次后,j>=0进入了一个匹配模式,可以进入while

假设一个模式串为 [] ... [] ...[] ...[]...       []代表一段重复文本

当 i指向第二段重复文本的开头,j 开始变化,直到遇到第二个... 结束..

为说明while循环:j 要跳到一个 j+1和i对应字符串的字符相等的位置,此时j之前的一段 和 ?到

i-1的那一段是一样的

跳转j 而要比较的字符位置是j+1 ,那么当j+1 和 i相等时,就找到了0~i的 最长前后缀。

时间复杂度

其中n为文本串长度,m为模式串长度,因为在匹配的过程中,根据前缀表不断调整匹配的位置,可以看出匹配的过程是O(n),之前还要单独生成next数组,时间复杂度是O(m)。所以整个KMP算法的时间复杂度是O(n+m)的。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/662194
推荐阅读
相关标签
  

闽ICP备14008679号