一文搞懂KMP算法

文章介绍了KMP算法,一种用于高效查找字符串模式的算法。它避免了暴力解法中不必要的字符比较,通过构建next数组来确定在不匹配时模式串应回退的位置,从而减少重复比较。next数组的生成是关键,它基于模式串自身的最长公共前后缀。
摘要由CSDN通过智能技术生成

模式匹配

从字符串(s)中查找某字符串(t)出现的位置,如果多次出现,则返回最小下标。比如 s="abaacdeacd",t="acd",查找的结果就是 3

暴力解法

这个方法思路简单。就是让 t 依次从 s 的每一个字符串开始比较,看有没有匹配的。最差时间复杂度为 O(nm),其中 n=len(s), m=len(t)

    public static int indexOfStr(String s, String t) {
        for(int i = 0; i < s.length() - t.length(); i++) {
            int j = 0;
            for(; j < t.length(); j++) {
                if(s.charAt(i + j) != t.charAt(j))
                    break;
            }
            if(j == t.length()) return i;
        }
        return -1;
    }

KMP算法

问题提出:暴力解法中,s 和 t 一失配,那么指向 s 和 t 的指针 i、j 都要往回移动,其中 i 自增到下一个字符位置,j 则回到 t 的起始位置。这里 j 回退到起始位置是合理的,但是 i 仅仅自增到下一字符的位置,就没那么必要了,因为会出现很多重复比较。因该利用某种机制减少这种不必要的比较。

例如下图当中,当 s 和 t 比对到那个位置的时候,发现 s[i] != t[j],这时候暴力解法就得让 i 回退到 index = 4的位置,而 j 则要回到 t 串的起始位置开始比较。

我们知道当 s[i] != t[j] 的时候,t[0,1,......, j-1]是和 s 串匹配的。如果我们知道 t[0,1,......, j-1] 的所有前缀和后缀子串中的最长匹配串的长度为 k,也就是 t[0, 1, ....., k-1] = t[j-k, ......, j-1],那么我们就知道 t[0, 1, ....., k-1] 和 s[i-j, i-j+1, ..., i] 的前 k 个字符是匹配的(也就是t[0]=s[i-j], t[1]=s[i-j+1], ..., t[k-1]=s[i-j+k-1]),那么 j 只需要回退到 k+1 的位置(为什么 i 不需要回溯呢?会不会漏解?不会漏解,因为我们选择的是最长的公共前后缀)。

如下图所示,j 回退到 t[3] 的位置,i 可以保持不变。

生成next数组

最难理解的地方就是生成这个next数组。该数组的定义是  “当 s[i] != t[j] 时,j 回退到的位置为 next[j]。从之前的分析可以知道,next[j]的值因该等于 t[0, 1, ......, j-1] 的最长公共前后缀的长度 k。如上面的例子,t[7]=d,t[0, ......, 6] 的最长公共前后缀长度为 3,所以 next[7]=3,也就是当 j 等于 7 发生 s[i]!=t[j] 时,j 应该回退到 t[3] 的位置。从next数组的定义可以知道,next数组只和模式串t有关系,跟 s 串没有关系,那也就说明不管 t 和谁去匹配,哪怕是和它自己去匹配(这一点很重要,对于为什么KMP算法中 k=next[k] 这一句的理解很重要),一旦发生失配,就让 j 回溯到 next[j] 的位置即可。

生成next数组的思路

    public int[] getNext(String t) {
        int[] next = new int[t.length()];
        next[0] = -1;
        int j = 1, k = 0;
        while(j < t.length() - 1) {
            if(k == -1 || t.charAt(j) == t.charAt(k)) {
                j++;
                k++;
                next[j] = k;
            } else {
                k = next[k];
            }
        }
        return next;
    }

生成next数组的过程,就是 t 和自己进行比对的过程,这和 t 跟 s 的比对过程的思想是一样的。这其中的关键还是next数据的定义。也是此算法思想的巧妙之处。你可以理解为 j 是那一个不回退的 i,而 k 是那一个回退到 next[j] 的 j。因为next数组的生成只和 t 有关,不管 t 和谁比较,发生失配的时候,都是回退到 next[j] 处,这也就是为什么有那句 "k=next[k]"。这里我绕了好久才绕明白。/(ㄒoㄒ)/~~

    public int indexOf(String s, String t) {  
    // KMP   
        int[] next = getNext(t);
    // next[0] 和 next[1]都应该是零,这是显然的
        next[0] = 0; 
        int j = 0;
        for(int i = 0; i < s.length(); i++) {
            if(s.charAt(i) == t.charAt(j)) j++;
            else j = next[j];
            if(j == t.length()) return i - j + 1;
        }
        return -1;
    }

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值