KMP字符串模式匹配算法【精简代码模板】

什么是模式匹配

模式匹配(Pattern Matching)就是在一个长度为n的文本S中,找某个长度为m的关键字P。

KMP算法如何来的?

KMP算法是由朴素字符串匹配算法优化而来,就是重新利用了朴素做法中浪费的一些有用信息。

  1. 不妨我们可以思考一下朴素做法如何实现。
    假设存在S = “abcde123”,P = “123”。

暴力实现模式匹配的方式:从S的第一个字符开始,逐个匹配P的每个字符。在第一轮的匹配中,P[0] != S[0],匹配失败,后面的P[1],P[2]就不用比较了。然后P向后挪一位继续匹配,直到P每位都匹配成功为止。
详细过程如下图:
在这里插入图片描述这种做法形象的看成一个滑块,在轨道S上滑动,直到完全匹配。

  1. 时间复杂度
    由于上面哪个例子比较特殊,P和S的字符基本上都不一样。由于在每一轮匹配中,往往第一个字符就匹配不上,用不着匹配后面,所以此时时间复杂度相对乐观,多不多为O(n)。这是暴力匹配能到的最优复杂度了。我们换一种情况,假设S= “aaaaaaaab”,P= “aab”。在这种情况下每次都是在匹配P的第三位字符出错,P移位后,P又从头开始匹配。这种时间复杂度就是O(n*m)。

在这里插入图片描述
3. 如何实现优化朴素做法
在朴素方法中,每次新的匹配都需要对比 S和 P的全部 m个字符,这实际上做了重复操作。例如第一轮匹配 S的前 3 个字符 “aaa” 和 P 的 “aab”,第二轮从 S 的第 2 个字符 ‘a’ 开始,与和 P 的第一个字符 ‘a’ 比较,这其实不必要,因为在第一轮比较时已经检查过这两个字符,知道它们相同。如果能记住每次的比较,用于指导下一次比较,使得 S 的 i 指针不用回溯,就能提高效率。

如何利用不回溯快速实现模式匹配

在KMP算法中,指向S串的i指针是不需要回溯的,只需要考虑指向P串的j指针的回溯即可。j回溯多少取决于S串中有多少能和P串匹配。

P串和S串出现不匹配之前有两种情况:

  1. P在不匹配之前的每个字符都不同
    例如 S = “abcabcd”,P= “abcd”,第一次匹配的失配点是 i=3,j=3。失配点之前的 P 的每个字符都不同,P[0]≠P[1]≠P[2];而失配点之前的 S与 P 相同,即 P[0]=S[0]、P[1]=S[1]、P[2]=S[2]。下一步如果按朴素方法,j要回到位置 0,i 要回到 1,去比较 P[0] 和 S[1]。但 i 的回溯是不必要的。由 P[0]≠P[1]、P[1]=S[1] 推出 P[0]≠S[1],所以 i 没有必要回到位置 1。同理,P[0]≠S[2],i 也没有必要回溯到位置 2。所以 i 不用回溯,继续从 i=3、j=0 开始下一轮的匹配。
    在这里插入图片描述在这里插入图片描述2. P在失配点之前的字符有部分相同
    (1) 相同的部分是前缀和后缀
    当 P 滑动到下面左图位置时,i和 j 所处的位置是失配点,j 之前的部分与 S 匹配,且子串 1(前缀)和子串 2(后缀)相同,设子串长度为 L。下一步把 P 滑到右图位置,让 P 的子串 1 和 S 的子串 2 对齐,此时 i 不变、j=L,然后开始下一轮的匹配。注意,前缀和后缀可以部分重合。
    在这里插入图片描述(2) 相同部分不是前缀和后缀
    下面左图,PP滑动到失配点 i 和 j,前面的阴影部分是匹配的,且子串 1 和 2 相同,但是 1 不是前缀(或者 2 不是后缀),这种情况与“1. P 在失配点之前的每个字符都不同”类似,下一步滑动到右图位置,即 i 不变,j 回溯到 0。

在这里插入图片描述

KMP的核心ne数组计算

在ne数组中,ne[j]数组就是P[0]-P[j-1] 这部分子串的前缀和后缀的最长交集的长度(最长公共前后缀)。

ne数组计算原理:
在这里插入图片描述

int ne[N];
for (int i = 2, j = 0; i <= m; i ++ )
{
    while (j && p[i] != p[j + 1]) j = ne[j];
    if (p[i] == p[j + 1]) j ++ ;
    ne[i] = j;
}

KMP模式匹配过程和求ne数组是一样的,只是换了两个不同的字符串进行操作。

KMP代码模板

求模式串的Next数组:
for (int i = 2, j = 0; i <= m; i ++ ){
    while (j && 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 && s[i] != p[j + 1]) j = ne[j];
    if (s[i] == p[j + 1]) j ++ ;
    if (j == m)
    {
        j = ne[j];
        // 匹配成功后的操作
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值