KMP算法图文详解

KMP算法是一种优化的字符串匹配方法,通过next数组减少不必要的字符比较,提高效率。该算法由Knuth、Morris和Pratt提出,核心在于利用已匹配信息避免冗余比较。Java实现中,`getNextArray`函数计算next数组,`getIndexOf`函数执行匹配过程。KMP算法时间复杂度为O(m+n),适用于大数据量的文本处理。
摘要由CSDN通过智能技术生成

KMP

KMP算法简介

KMP算法是一种改进的字符串匹配算法,由D.E.KnuthJ.H.MorrisV.R.Pratt提出的,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。**KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。**具体实现就是通过一个next()函数实现,函数本身包含了模式串的局部匹配信息。KMP算法的时间复杂度O(m+n)

字符串的模式匹配

字符串的模式匹配是一种常用的运算。所谓模式匹配,可以简单地理解为在目标(字符串)中寻找一个给定的模式(也是字符串),返回目标和模式匹配的第一个子串的首字符位置。通常目标串比较大,而模式串则比较短小

如下图:

在这里插入图片描述

S1是目标串,S2是模式串,要在S1中寻找S2,最后返回S2出现在S1中的首字符的位置。

先思考一下朴素做法:

可以定义两个指针 iji 用于遍历S1j 用于遍历S2

在这里插入图片描述

一开始ij都是指向字符串的首位,发现不相等,S2字符串不断向后移动,如果发现 ij 所指向的字符相等,ij 都向后面移动,继续判断两个指针所指向的字符是否相等。

在这里插入图片描述

最终,指针j遍历完S2后发现所有字符都匹配上了,于是就返回第一个子串的首字符位置: i - j + 1

以上情况是成功找到的情况,但是实际可能遍历到最后都没有匹配成功,并且浪费了大量的时间和资源。

在这里插入图片描述

如上图所示,如果当目标串和模式串都比较大时,时间复杂度会非常高: O(n*m)

所以需要进行改进。在遍历过程中,一定会出现一些字符的冗余比较,而kmp算法中核心的next就是为了避免冗余的比较而设计的,next数组中每一个数对应每一个个字符的下标,而这每一个数都代表着一个信息,就是前面的字符串中最长且相等的前缀和后缀的长度。同时要注意,next数组是与模式串向匹配的信息。

在这里插入图片描述

比如上图,字符 c 之前的最长且相等的前缀串和后缀串是 abc 并且长度是3,所以next数组在 c 位置上的信息就是 3 。

在这里插入图片描述

如上图,next数组中保存的信息就会起到作用,在 i 移动到字符 t 的位置, j 移动到字符 c 位置的时候,发现不相等那么根据 c 位置对应的next数组的信息是 3 ,那么就可以将j移动到 3 的位置再进行比较。

在这里插入图片描述

如果发现 ij 指向的字符还是不等的话就再根据next数组中对应的信息将j指针回退到一定的位置。这样就可以大幅度的减少 j 指针移动的次数和比较字符的次数,从而降低时间复杂度。

实现KMP算法

JAVA代码:

public class KMP {
    public static int getIndexOf(String s1, String s2) {
        if (s1 == null || s2 == null || s2.length() < 1 || s1.length() < s2.length()) {
            return -1;
        }
        char[] str1 = s1.toCharArray();
        char[] str2 = s2.toCharArray();

        int x = 0, y = 0;
        int[] next = getNextArray(str2);
        while (x < str1.length && y < str2.length) {
            if (str1[x] == str2[y]) {
                x++;
                y++;
            } else if (next[y] == -1) {
                x++;
            } else {
                y = next[y];
            }
        }
        return y == str2.length ? x - y : -1;
    }

    public static int[] getNextArray(char[] match) {
        if (match.length == 1) {
            return new int[]{-1};
        }
        // 定义next数组
        int[] next = new int[match.length];
        next[0] = -1;// 第一个字符前没有任何字符,所以特殊处理,就规定它对应的next数组值是-1
        next[1] = 0; // 第二个字符前面只有一个字符,所以最大相等的前缀和后缀长度一定是1
        int i = 2; // 在那个位置上求next数组的值
        int cn = 0;
        
        while (i < next.length) {
            if (match[i - 1] == match[cn]) { // 匹配成功的时候
                // 这一行代码实际就是下标换算的过程,包含三个操作: (1)将cn++ (2)将cn+1后的值赋给next[i] (3) 将i向后移动也就是i++
                next[i++] = ++cn;
            } else if (cn > 0) {
                // 上面的判断不成立, 就对cn做回退处理
                cn = next[cn];
            } else {
                // 如果cn回退到0的位置了,那么说明没有相等的前缀和后缀,就将此处设置为0
                next[i++] = 0;
            }
        }
        return next;
    }
}

简化后的模板代码:

// s[]是长文本,p[]是模式串,n是s的长度,m是p的长度
求模式串的Next数组:
for (int i = 2, j = 0; i <= m; i ++ )
{
    while (j > 0 && p[i] != p[j + 1]) j = ne[j];
    if (p[i] == p[j + 1]) j ++ ;
    ne[i] = j;
}

匹配
for (int i = 1, j = 0; i <= n; i ++ )
{
    while (j > 0 && s[i] != p[j + 1]) j = ne[j];
    if (s[i] == p[j + 1]) j ++ ;
    if (j == m)
    {
        j = ne[j];
        // 匹配成功后的逻辑
    }
}

例题:
acwing 831. KMP字符串
leetcode 28. 实现 strStr()
leetcode 214. 最短回文串

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

神烦狗闯入了你的博客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值