kmp 算法

前言

kmp 是很早之前看的算法,现在又重新看了一下,感觉这次的理解更深了。
kmp 算法是一个非常精妙的算法,他巧妙的避免了重复的遍历值。

原理

KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是通过一个next()函数实现,函数本身包含了模式串的局部匹配信息。KMP算法的时间复杂度O(m+n)。

next数组

kmp 的核心是构建next数组,其中next数组记录的是如果字符串匹配失败 我们将从什么地方进行匹配。

下面,我们可以进行查看next 数组的构建
其中,next 数组的长度与被匹配的字符串相等。

位置 0 1 2 3 4 5
字符串 a b a b b a
next -1 0 0 1 2 0

我们可以看到next[i] 的值指的是我们如果失败,我们将从那个位置继续查找。例如如果第一个位置失败,这个代表我们没有位置可以继续,继续运行 如果是第3个位置失败 代表我们从第一个位置继续查找。

也就是说 next[i] = j,代表 pattern[0,j) = pattern[i-j,i) 这个j是满足公式的最大值。
通俗的讲,就是从0开始的子串,与到i-1的子串,有多少相同的数量。
其中next[0] = -1;

例子:abcabd
next[0] = -1
next[1] = 0
next[2] = 0
next[3] = 0
next[4] = 1
next[5] = 2

既然我们知道next数组的求解方法,那么下面我将用2种方法来进行求解,第一种是比较笨拙的方法,第二种是比较常用的方法。

笨拙求解next 数组

一种比较笨拙的方法是采用三重循环,判断next[i]前面有几个相同,以相同字母的数量最多的为准,

public int[] getNextStupid(String ps){
        int[] next = new int[ps.length()];
        next[0] = -1;
        for(int index = 1;index<ps.length();index++){
            int isRight = 0;
            for(int j=1;j<index;j++){
                boolean flag = true;//判断是否是一样的
                for(int k=0;k<j;k++){
                    if(ps.charAt(k)!=ps.charAt(index+k-j)){
                        flag = false;
                        break;
                    }
                }
                if(flag){
                    isRight = j;
                }
            }
            next[index] = isRight;
        }
        return next;
    }

常用的next数组求解办法

其实我们不需要向上面那样,每一次都要求,因为next数已经记录着相同字符串的数量,而后一个相同字符数量是由前一个相同字符数量决定,我们可以采用一个动态规划的方法求得next[i]的值。

    public  int[] getNext(String ps) {
        int[] next = new int[ps.length()];
        int index = 1;// 读取当前的字符串的位置
        int pre = -1;//  代表 位置 ps[0 至 pre] 与 ps[index - pre 至 index-1] 是相同的 pre<index
        next[0] = -1;// 0 位置前面是没有任何相同
        for(;index<ps.length();index++){
            //如果前面没有值或者str[index-1]的值和str[pre]的值相同,那么我们直接查找的位置+1
            if(pre==-1||ps.charAt(index-1) == ps.charAt(pre)){
                pre++;
                next[index] = pre;
                continue;
            }
            // 如果匹配失败,我们从next数组中找到下一个匹配的位置
            pre = next[pre];
        }
        return next;
    }

查找

既然我们已经构建next成功,我们就可以直接使用了

public int index(String str,String pattern){
        int[] next = getNext(pattern);
        int i = 0;
        int j = 0;
        while (i<str.length()&&j<pattern.length()){
            if(str.charAt(i)==pattern.charAt(j)){
                i++;
                j++;
            }else {
                if(j==0){
                    i++; // 当str[i]与pattern[0]个不同时 我们需要匹配下一个字符
                }else {
                    j = next[j];
                }
            }
        }
        if(j!=pattern.length()){
            return -1; // 匹配失败
        }
        return i-pattern.length();
    }

完整代码

package com.company;

import javax.security.sasl.SaslServer;
import java.util.Arrays;

public class KMP {
    public  int[] getNext(String ps) {
        int[] next = new int[ps.length()];
        int index = 1;// 读取当前的字符串的位置
        int pre = -1;//  代表 位置 ps[0 至 pre] 与 ps[index - pre 至 index] 是相同的 pre<index
        next[0] = -1;// 0 位置前面是没有任何相同
        for(;index<ps.length();index++){
            //如果前面没有值或者str[index]的值和str[pre]的值相同,那么我们可以直接从那个位置开始查找
            if(pre==-1||ps.charAt(index-1) == ps.charAt(pre)){
                pre++;
                next[index] = pre;
                continue;
            }
            // 如果匹配失败,我们从next数组,前一个相同的位置在进行匹配
            pre = next[pre];
        }
        return next;
    }
    public int[] getNextStupid(String ps){
        int[] next = new int[ps.length()];
        next[0] = -1;
        for(int index = 1;index<ps.length();index++){
            int isRight = 0;
            for(int j=1;j<index;j++){ // 0个相同我们不需要看,所以我们从1开始出发,其最多也只能是index-1个相同
                boolean flag = true;//判断是否是一样的
                for(int k=0;k<j;k++){
                    if(ps.charAt(k)!=ps.charAt(index+k-j)){
                        flag = false;
                        break;
                    }
                }
                if(flag){
                    isRight = j;
                }
            }
            next[index] = isRight;
        }
        return next;
    }
    public int index(String str,String pattern){
        int[] next = getNext(pattern);
        int i = 0;
        int j = 0;
        while (i<str.length()&&j<pattern.length()){
            if(str.charAt(i)==pattern.charAt(j)){
                i++;
                j++;
            }else {
                if(j==0){
                    i++; // 当str[i]与pattern[0]个不同时 我们需要匹配下一个字符
                }else {
                    j = next[j];
                }
            }
        }
        if(j!=pattern.length()){
            return -1; // 匹配失败
        }
        return i-pattern.length();
    }
}
// KMP Test
class KmpTest{
    public static void main(String[] args) {
        KMP kmp = new KMP();
        String s1 = "aaaaaaaaabcdeeeeaassd";
        String s2 = "bcde";
        System.out.println(kmp.index(s1,s2));
    }
}



posted @   度一川  阅读(197)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
点击右上角即可分享
微信分享提示