KMP算法-附详细注释

public class KMP {

    public static void main(String[] args) {
        String mainStr = "aabaabaaf"; // 定义主串
        String patt = "aabaaf"; // 定义模式串

        int KMP_result = new KMP().indexOf(mainStr,patt);
        System.out.println(KMP_result); // 3
        System.out.println(KMP_result==mainStr.indexOf(patt)); // true
    }

    public int indexOf(String str, String patt){
        // 求patt在str中的索引,功能同:str.indexOf(patt)

        // 如果模式串为空,返回0
        if (patt == "") return 0;

        // 无法匹配,返回-1
        if (str.length()<patt.length()) return -1;

        // 构建next数组
        int[] next = new int[patt.length()];
        createNext(next,patt);

        int i = 0; // i为主串索引
        int j = 0; // j为模式串索引
        while (i<str.length()&&j<patt.length()){
            if (str.charAt(i)==patt.charAt(j)){
                // 若相等,i和j索引同时后移
                i++;
                j++;
            }else if (j==0) {
                // 若不相等,且j==0,只后移主串索引
                i++;
            }else {
                // 若不相等,且j!=0,则主串索引不动,模式串索引改为next[j]
                j = next[j];
            }
        }
        // 有三种情况会导致跳出上面的while循环,
        // 情况1:j==patt.length()
        // 情况2:j==patt.length() 的同时 i==str.length()
        // 情况3:i==str.length()
        if (j == patt.length()){
            return i-j; // 情况1或2,匹配成功
        }else {
            return -1; // 情况3,匹配失败
        }
    }

    public void createNext(int[] next, String patt){
        // 计算next数组,如:next[i]表示patt[:i]的最长前后缀
        // a  a  b  a  a  f
        // -1 0  1  0  1  2

        // 初始化
        int j = 0;
        next[0] = -1;
        if (next.length==1) return;
        next[1] = 0; // patt[:1] (即,patt[0]) 没有子串

        // 从i=2开始遍历模式串的索引,将结果存入next[i]
        // j指向前缀的最后一个字符,i-1指向后缀的最后一个字符
        // 每次for循环的目的是寻找patt[:i]的最长公共前后缀
        // 进一步说,每次for循环要定位j的值,使得patt[:j+1]与patt[i-j-1:i]为最长公共前缀和后缀
        // i-j-1不重要,可以忽视它,这句话可改为:使得patt[:j+1]与patt[~:i]为最长公共前缀和后缀
        // 定位j的值后,意味着patt[:i]存在公共前缀和后缀,并将next[i]赋值为j++(即,公共前后缀的长度)
        // 若patt[:i]不存在公共前缀和后缀,此时j=0,将next[i]赋值为j
        for (int i=2; i<next.length; i++){
            // 以i=10为例,这次for循环的目的是寻找patt[:10]的最长公共前后缀
            // 代码运行到这里时,如果j是>0的(以j=4为例),说明patt[:4]和patt[~:9]数值上是相等的
            // 此时需要判断patt[4]是否=patt[9] (即,判断patt[j]是否=patt[i-1])
            while (j>0 && patt.charAt(j)!=patt.charAt(i-1)){
                // 如果patt[4]!=patt[9],则通过while循环继续比较patt[next[j]]是否=patt[9]
                // 此处需要多想,脑子里形成动画
                j = next[j];
            }
            // 如果patt[4]==patt[9],那说明patt[:5]和patt[~:10]数值上相等,最长前后缀为5
            // 此时j=4,先j++,然后next[10]=j
            if (patt.charAt(i-1)==patt.charAt(j)){
                // 代码运行到这里时,如果j是=0的,只需要判断patt[0]是否=patt[9]即可
                // 如果patt[0]==patt[9],next[10]=1,否则,next[10]=0
                j++;
            }
            next[i] = j;
        }
    }
}

  • 10
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值