KMP字符串匹配算法

简介: KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。

什么是KMP算法?


KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。


暴力搜索算法实现


1654650484979.png

请问,在字符串 T 中是否包含 P 的 "ababc"?


我们可以从第一个字符开始比对,如下图:

1654650591021.png

在第四次比对的时候,我们发现 T 字符串和 P 字符串并不一致。


我们将字符串 P 整体后移一位重新进行对比。

1654650625221.png

第一次比对我们就发现 T 字符串和 P 字符串比对不上。所以我们需要继续后移 P 字符串进行对比。

1654650672039.png

以此类推,当所有字符能匹配上则说明匹配成功,如果匹配到 T 的末尾都没有成功的话则失败,此算法效率很低。


KMP算法实现


1654650734454.png

请问,在字符串 T 中是否包含 P 的 "ababc"?


第一步:

在学习KMP算法之前,我们需要先了解前缀表(Prefix Table)。


我们先做的就是找到字符串长度较短的字符串,也就是字符串 P。

a


a b


a b a


a b a b


一共有四个前缀。


第二步:

我们需要找出最长公共前后缀并且比原始字符串短。


这句话有点不太好理解,我们使用a b a b来举例子。

1654650849004.png

我们可以看到 a b a b 最长前缀就是 a b a,最长后缀就是 b a b。

1654650877010.png

但是我们发现这两个字符串明显是不一样的。

1654650899579.png

3个字符的前后缀明显不同,所以我们使用2个字符前后缀进行匹配看看。


我们发现 前缀是 a b 后缀也是 a b 。这样我们就知道最长公共前后缀是2。

1654650949334.png

然后我们继续往上看 a b a 很明显它的公共前后缀就是1。如果我们用2来看的话就是 ab 和 ba 很明显是不同的。


ok。再往上就是 a b 很明显,1是不匹配的。所以最长公共前后缀是0。 a 也一样。


我们看下最终是怎样的。

1654651020891.png

我们进行整体向右移动一位,然后初始值赋为 -1 可以得到如下结果:


a b a b c
-1 0 0 1 2

这样我们去进行和 T 字符串进行比对,当比对到下标为3时,我们发现 a 和 b不同匹配失败。


1654651153171.png

这个时候我们应该怎么做?


我们发现在 P 数组中下标为3的 b 匹配错误,而 b 的前缀表是 1,我们需要将 P 数组下标为 1 的位置 移动 T 数组匹配失败的位置。

1654651192514.png

但是我们发现匹配还是错误的,我们可以知道 b 的 前缀表是 0 所以我们让下标为0的位置整体移动到 T 数组匹配失败的位置。

1654651214650.png

我们重新进行匹配,发现第一个 a 相等,第二个 b 和 c 不相等。

1654651229681.png

我们继续移动,b 的前缀表是 0 所以我们让下标为0的位置整体移动到 T 数组匹配失败的位置。但是还是 a 和 c 匹配失败。

1654651247339.png

但是我们知道 a 的前缀表是 -1 也就是一个空的字符串,我们将空的字符串对准匹配失败的位置,等同于-1就是整体右移一位

1654651264439.png

这个时候我们继续进行匹配,发现已经匹配成功了!

1654651282473.png

暴力搜索和KMP区别


举一个例子:

1654651308809.png

如果我们使用暴力搜索的话我们会发现我们需要将 P 字符串一直后移一位,需要四次后移才可匹配成功。


如果我们使用KMP来解决的话,还是首先我们需要知道前缀表是多少,如下图:

1654651327795.png


a a a a b
-1 0 1 2 3


然后我们进行比对。

1654651379582.png

前缀表为3匹配失败,将数组下标为3移动至此,继续进行对比。

1654651404271.png

需要注意的是,我们不需要再重头开始对比,只需要从下标为3的位置开始匹配即可

后面其实就是大同小异,继续后移匹配即可。


总结


由此我们可以知道,暴力搜索每次移动后都要从第一位开始重新匹配,而我们用KMP的话我们不需要再重头开始对比可以省去大量时间。


Java代码实现KMP算法


1654651520635.png

使用Java代码如何先得到前缀表。

private static void buildPrefixTable(char[] pattern, int[] prefix) {
        // 对比指针下标
        int len = 0;
        // 第一位不需要进行比对因为肯定是0
        prefix[0] = 0;
        //因为第一位不需要进行比对,我们从1开始
        int i = 1;
        //最后一位不需要比较因为我们需要的前缀表是比原始字符串短
        int length = prefix.length -1;
        while (i < length) {
            if (pattern[i] == pattern[len]) {
                //匹配成功,自增继续匹配下一位字符
                len++;
                prefix[i] = len;
                i++;
            } else {
                if (len > 0) {
                    //获得前一位的前缀
                    len = prefix[len - 1];
                } else {
                    //没有找到只能是0,i++继续匹配下一位
                    prefix[i] = len;
                    i++;
                }
            }
        }
        //整体后移一位,将第一位修改为-1
        for (int j = prefix.length - 1; j > 0; j--) {
            prefix[j] = prefix[j - 1];
        }
        prefix[0] = -1;
    }

整体逻辑代码如下:


public static void main(String[] args) {
        String T = "ABABABCABAABABABAB";
        String P = "ABABCABAA";
        char[] text = T.toCharArray();
        char[] pattern = P.toCharArray();
        kmpSearch(text, pattern);
    }
    private static void kmpSearch(char[] text, char[] pattern) {
        int m = text.length;
        int n = pattern.length;
        int[] prefix = new int[n];
        buildPrefixTable(pattern, prefix);
        // 字符串 text 的指针下标
        int i = 0;
        // 字符串 pattern 的指针下标
        int j = 0;
        while (i < m) {
            if (j == n - 1 && text[i] == pattern[j]) {
                System.out.println("找到了!开始下标为:" + (i - j));
                j = prefix[j];
                //如果未比较的pattern长度已超出text剩下的长度则提前结束
                if (n - j > m - i) {
                    break;
                }
            }
            if (text[i] == pattern[j]) {
                //相同的话自增匹配下一位
                i++;
                j++;
            } else {
                //不同的话就将前缀值当下标进行整体移动
                j = prefix[j];
                if (j == -1) {
                    //如果值为-1则说明需要整体后移一位进行匹配
                    i++;
                    j++;
                }
            }
        }
    }
    /**
     * 构建前缀表
     */
    private static void buildPrefixTable(char[] pattern, int[] prefix) {
        // 对比指针下标
        int len = 0;
        // 第一位不需要进行比对因为肯定是0
        prefix[0] = 0;
        //因为第一位不需要进行比对,我们从1开始
        int i = 1;
        //最后一位不需要比较因为我们需要的前缀表是比原始字符串短
        int length = prefix.length -1;
        while (i < length) {
            if (pattern[i] == pattern[len]) {
                //匹配成功,自增继续匹配下一位字符
                len++;
                prefix[i] = len;
                i++;
            } else {
                if (len > 0) {
                    //获得前一位的前缀
                    len = prefix[len - 1];
                } else {
                    //没有找到只能是0,i++继续匹配下一位
                    prefix[i] = len;
                    i++;
                }
            }
        }
        //整体后移一位,将第一位修改为-1
        for (int j = length; j > 0; j--) {
            prefix[j] = prefix[j - 1];
        }
        prefix[0] = -1;
        System.out.println("前缀表为:"+Arrays.toString(prefix));
    }


输出结果:


前缀表为:[-1, 0, 0, 1, 2, 0, 1, 2, 3]
找到了!开始下标为:2
相关文章
|
6天前
|
算法
【算法】滑动窗口——找到字符串中所有字母异位词
【算法】滑动窗口——找到字符串中所有字母异位词
|
4天前
|
算法 Java
掌握算法学习之字符串经典用法
文章总结了字符串在算法领域的经典用法,特别是通过双指针法来实现字符串的反转操作,并提供了LeetCode上相关题目的Java代码实现,强调了掌握这些技巧对于提升算法思维的重要性。
|
1天前
|
算法
KMP算法
KMP算法
4 0
|
1月前
|
自然语言处理 算法 搜索推荐
字符串相似度算法完全指南:编辑、令牌与序列三类算法的全面解析与深入分析
在自然语言处理领域,人们经常需要比较字符串,这些字符串可能是单词、句子、段落甚至是整个文档。如何快速判断两个单词或句子是否相似,或者相似度是好还是差。这类似于我们使用手机打错一个词,但手机会建议正确的词来修正它,那么这种如何判断字符串相似度呢?本文将详细介绍这个问题。
205 1
|
1月前
|
数据采集 算法 JavaScript
揭开JavaScript字符串搜索的秘密:indexOf、includes与KMP算法
JavaScript字符串搜索涵盖`indexOf`、`includes`及KMP算法。`indexOf`返回子字符串位置,`includes`检查是否包含子字符串。KMP是高效的搜索算法,尤其适合长模式匹配。示例展示了如何在数据采集(如网页爬虫)中使用这些方法,结合代理IP进行安全搜索。代码示例中,搜索百度新闻结果并检测是否含有特定字符串。学习这些技术能提升编程效率和性能。
揭开JavaScript字符串搜索的秘密:indexOf、includes与KMP算法
|
16天前
|
算法 C++
惊爆!KPM算法背后的秘密武器:一行代码揭秘字符串最小周期的终极奥义,让你秒变编程界周期大师!
【8月更文挑战第4天】字符串最小周期问题旨在找出字符串中最短重复子串的长度。KPM(实为KMP,Knuth-Morris-Pratt)算法,虽主要用于字符串匹配,但其生成的前缀函数(next数组)也可用于求解最小周期。核心思想是构建LPS数组,记录模式串中每个位置的最长相等前后缀长度。对于长度为n的字符串S,其最小周期T可通过公式ans = n - LPS[n-1]求得。通过分析周期字符串的特性,可证明该方法的有效性。提供的C++示例代码展示了如何计算给定字符串的最小周期,体现了KPM算法在解决此类问题上的高效性。
26 0
|
1月前
|
算法 Java
KMP算法详解及其在字符串匹配中的应用
KMP算法详解及其在字符串匹配中的应用
|
6天前
|
算法
基于模糊控制算法的倒立摆控制系统matlab仿真
本项目构建了一个基于模糊控制算法的倒立摆控制系统,利用MATLAB 2022a实现了从不稳定到稳定状态的转变,并输出了相应的动画和收敛过程。模糊控制器通过对小车位置与摆的角度误差及其变化量进行模糊化处理,依据预设的模糊规则库进行模糊推理并最终去模糊化为精确的控制量,成功地使倒立摆维持在直立位置。该方法无需精确数学模型,适用于处理系统的非线性和不确定性。
基于模糊控制算法的倒立摆控制系统matlab仿真
|
5天前
|
机器学习/深度学习 算法 定位技术
MATLAB - 遗传算法(GA)求解旅行商问题(TSP)
MATLAB - 遗传算法(GA)求解旅行商问题(TSP)
12 3
|
7天前
|
算法
基于多路径路由的全局感知网络流量分配优化算法matlab仿真
本文提出一种全局感知网络流量分配优化算法,针对现代网络中多路径路由的需求,旨在均衡分配流量、减轻拥塞并提升吞吐量。算法基于网络模型G(N, M),包含N节点与M连接,并考虑K种不同优先级的流量。通过迭代调整每种流量在各路径上的分配比例,依据带宽利用率um=Σ(xm,k * dk) / cm来优化网络性能,确保高优先级流量的有效传输同时最大化利用网络资源。算法设定收敛条件以避免陷入局部最优解。

热门文章

最新文章