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;
    }
}