• Java实现kmp算法,少量注释


    kmp算法用来求解"字符串p在字符串s中的首次出现位置"这样的问题。

    暴力法就不谈了,这里介绍kmp算法。

    考虑这样一种情况:

    s = "a b a b a b a b c"

    p = "a b a b c"

    上面标红的地方是两个字符串首次不相等的地方,不相等就要将指针后移,进行重新匹配,那么将指针后移多少呢?

    根据kmp算法,这里要后移成这样:

    "a b aa b a b c"

          "a b a b c"

    上面标红的地方就是下次开始比较的地方。

    解释为什么这样移动:

      上面的例子中,指针在index为4的地方停下来,说明index为4之前的字符串是相等的,在index为4的地方发生了不匹配。

      我们可以得到:对于字符串s和p,他们的0-3个字符是相等的。设t = s.substring(0,4);即t为s或者p的0-3.

      因为t的最长公共前后缀为"ab", 所以"ab"不用再次比较了,进而从"ab"的下一位开始比较即可。

      对于最长公共前后缀,我们只求p的,而且用next数组来表示。

    经过测试

    字符串长度为50000的时候,暴力法用时约为10ms,kmp用时约为6ms。

    测试的时候,一个有趣的问题出现了:java: 常量字符串过长

    原因:是用String xx = "";来创建的字符串。在IDEA中,字符串长度超过65535,IDEA会提示java: 常量字符串过长。使用javac 进行编译也会有类似的提示

    解决办法:用new关键字将字符串创建为一个对象。因为堆空间是足够的。

    package test;
    
    
    public class kmp {
        public static void main(String[] args) {
            long before = System.currentTimeMillis();
            System.out.println(new kmp().getSubstringIdx("abababc", "ababc"));
            long after = System.currentTimeMillis();
            System.out.println((after-before)+"ms");
        }
        //kmp算法, 返回字符串substring在字符串s中的首次出现位置,没找到就返回-1
        public int getSubstringIdx(String s, String substring){
            int[] next = getNext(substring);
            int i = 0, j = 0;//i指向s的开始, j指向substring的开始
            int n1 = s.length(), n2 = substring.length();
            while(i<n1 && j<n2){
                int tempI = i;//记录i的位置
                while(s.charAt(i) == substring.charAt(j)){//开始比较
                    i++;
                    j++;
                    if(j == n2)return i-n2;//找到了
                    if(i == n1)return -1;
                }
                //没找到,要回退,j向前移动
                if(j>0)j = next[j-1];
            }
            return -1;//没找到
        }
    
        //得到next数组,即求substring的最长公共前后缀的长度。
        public int[] getNext(String substring){
            int[] next = new int[substring.length()];
            next[0] = 0;
            int j = 0, i = 1;//i指向后缀的末尾,j指向前缀的末尾
            while(i<substring.length()){
                if (substring.charAt(i) == substring.charAt(j))next[i++] = ++j;//两个后缀末尾字符相等,则i,j都向前移动
                else{
                    /*
                    //末尾字符不相等,退而求其次,前缀后退,后缀不动。
                    不懂这一步,可以举例子:
                        a b a b c
                        0 0 1 2 0
                     */
                    if (j > 0)j = next[j - 1];
                    else next[i++] = 0;//j已经退到0了,不能再退了,则next[i]=0,i++;
                }
            }
            return next;
        }
    
        //暴力法
        public int getSubstringIdxBF(String s, String substring){
            for(int i=0;i<s.length();i++){
                int tempI = i;
                for(int j=0;j<substring.length();j++){
                    if (s.charAt(i) == substring.charAt(j)){
                        if(j == substring.length()-1)return tempI;
                        i++;
                    }
                    else{
                        i = tempI;
                        break;
                    }
                }
            }
            return -1;
        }
    }
  • 相关阅读:
    JavaScript-4(循环语句)
    JavaScript-3
    JavaScript-2(基本运算)
    JavaScript-1
    HTML基础-3(框架、表单与表格等)
    HTML基础-2(内容容器等)
    HTML基础-1(b,i,u命令等)
    【CSP-S2019模拟】10.07比赛总结
    JZOJ6380. 【NOIP2019模拟2019.10.06】小w与最长路(path)
    【CSP-S2019模拟】10.06比赛总结
  • 原文地址:https://www.cnblogs.com/xxxxxiaochuan/p/14127182.html
Copyright © 2020-2023  润新知