首页 > 其他分享 >KMP——字符串匹配的利器

KMP——字符串匹配的利器

时间:2022-11-13 08:00:29浏览次数:40  
标签:dots nxt 匹配 ne 利器 KMP 字符串 Rightarrow

默认所有字符串的下标从 \(1\) 开始。

\(\text{KMP(Knuth-Morris-Pratt)}\) 算法,能够在 \(O(|s| + |p|)\) 的时间复杂度内求出模式串 \(p\) 在文本串 \(s\) 中的出现次数、位置等信息。

众所周知,暴力地进行匹配(也就是对 \(s\) 的每一位都进行与 \(p\) 的比对)的最坏时间复杂度是 \(O(|s| \times |p|)\),那么 \(\text{KMP}\) 算法是如何将时间复杂度优化这么多的呢?

从暴力的过程入手:不难发现,在匹配的过程中,有很多重复的比对。举个例子,令 $s = $ abab,$p = $ ab,当我们匹配到 \(s[2]\) 时,明明已经知道它和 \(p[2]\) 相等,而 \(p[2] \ne p[1]\),却还是要在匹配一次。这样的例子数不胜数,放到较长的 \(s\) 和 \(p\) 中,浪费的资源会更多。而 \(\text{KMP}\) 算法就解决了这个问题。

先引入一个 \(nxt[]\) 数组,令 \(suf(i)\) 表示 \(p[1 \dots i]\),即 \(p\) 以第 \(i\) 个字符为结尾的前缀,\(nxt[i]\) 表示 \(suf(i)\) 的每个前缀和后缀的最长公共元素长度,特别地,\(nxt[1] = 0\)。

真前缀:不等于原字符串的前缀。

真后缀:不等于原字符串的后缀。

再举例:对于 $p = $ abcabcd

\(nxt[1] = 0\)

\(nxt[2] = 0\)

\(nxt[3] = 0\)

\(nxt[4] = 1,(p[1] = p[4])\)

\(nxt[5] = 1,(p[1 \dots 2] = p[4 \dots 5])\)

\(nxt[6] = 3,(p[1 \dots 3] = p[4 \dots 6])\)

有了 \(nxt[]\),在失配之后就可以整些高大上的操作了。

再再举例: $s = $ abcabcacd,$p = $ abcabcd 时(配对成功往后跳即可,这里只列举失配的情况)

当 \(i = 7, k = 6\) 时,\(s[i] \ne p[k + 1]\),让 \(k = nxt[k] = 3\),此时 \(s[i] = s[k + 1]\),继续匹配。

可以发现,\(nxt[]\) 帮助我们跳过了 \(s[3 \dots 5]\) 的再次匹配的过程,进而将匹配的时间复杂度优化到了 \(O(|s|)\)。

清楚了这个逻辑,不难写出匹配的代码:

int KMP(char *s, char *p) { //求解出现次数
    int res = 0, k = 0, slen = strlen(s + 1), plen = strlen(p + 1);
    for (int i = 1; i <= slen; i++) {
        while (k && s[i] != p[k + 1]) k = nxt[k]; // k==0时就不必再跳了,不然会死循环
        if (s[i] == p[k + 1]) k++;
        if (k == plen) res++;
    }
    return res;
}

接下来,难点来到了如何求解 \(nxt[]\)。

这里给出递推求 \(nxt[]\) 的方法;

还是就着上面的例子来解释:\(p =\) abcabcd

\(nxt[1] = 0\)

\(i = 2, k = 0, p[i] \ne p[k + 1] \Rightarrow nxt[2] = k = 0\)

\(i = 3, k = 0, p[i] \ne p[k + 1] \Rightarrow nxt[3] = k = 0\)

\(i = 4, k = 0, p[i] = p[k + 1] \Rightarrow nxt[4] = k = 1\)

\(i = 5, k = 1, p[i] = p[k + 1] \Rightarrow nxt[5] = k = 2\)

\(i = 6, k = 2, p[i] = p[k + 1] \Rightarrow nxt[6] = k = 3\)

\(i = 7, k = 3, p[i] \ne p[k + 1] \Rightarrow k = nxt[k] = 0, p[i] \ne p[k + 1] \Rightarrow nxt[7] = k = 0\)

不难发现,\(nxt[]\) 的求解过程,其实就是 \(p\) 和 \(p\) 的 \(\text{KMP}\)。

求解代码如下:

void getnxt(char *p) [ //求解nxt[]
    nxt[1] = 0;
    int plen = strlen(p + 1), k = 0;
    for (int i = 2; i <= plen; i++) {
        while (k && p[i] != p[k + 1]) k = nxt[k];
        if (p[i] == p[k + 1]) k++;
        nxt[i] = k;
    }
]

至此,我们便可以在 \(O(|s| + |p|)\) 的时间复杂度内进行字符串匹配了~

标签:dots,nxt,匹配,ne,利器,KMP,字符串,Rightarrow
From: https://www.cnblogs.com/chy12321/p/16885352.html

相关文章

  • 又臭又长的字符串处理(简易内存池)
    #include<iostream>#include<vector>#include<string>usingnamespacestd;intmax=100;//分配函数voidalloc(vector<vector<int>>&address,intsize){ if(si......
  • Redis字符串(String)
    String是Redis最基本的类型,你可以理解成与Memcached一模一样的类型,一个key对应一个value。常用命令set<key><value>添加键值对get<key>查询对应键值append<......
  • 来自学长的字符串2
    我们抬头仰望同一片星空,在万家灯火熄灭的时候……本来打算先改考试题在再finish,结果鹤了一下午+半个晚上也没鹤对,鹤的T2还被卡了却依然不知道为什么那个贪心是假的……......
  • Python字符串操作
    Python字符串操作1.*字符串的常用操作1.*.&访问字符串中的值Python访问子字符串变量,可以使用方括号来截取字符串。与列表的索引一样,字符串索引从0开始。字符串的索引......
  • 判断是否为回文字符串, StringBuffer对象的reverse()方法,返回值toString()
      import java.util.*;public class Solution {    /**     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可    ......
  • 元进网络自动化(1)---利器eNSP优化
    在16G内存的电脑上,eSNP开三个路由器就卡的不行,感觉肯定有问题。经过仔细琢磨,需要在VM里面优化内存、CPU和显存配置,同时eNSP设置系统内存保护去勾选。优化后,打开八个路由器都......
  • KMP算法
    KMP算法是一个字符串匹配算法,或者说是一个子字符串匹配算法算法在字符串str中搜索子串pattern,如果存在,返回这个子串的起始索引,如果不存在,返回-1暴力枚举匹配暴力的字符......
  • 第四十四章 在CSP应用程序中本地化文本 - 显示本地化字符串的其他选项
    目录第四十四章在CSP应用程序中本地化文本-显示本地化字符串的其他选项显示本地化字符串的其他选项%response.GetTextMethodFormatTextMethod$$$FormatTextMacrosMat......
  • 340. Longest Substring with At Most K Distinct Characters 最多k个字母不同的字符
    Givenastring s andaninteger k,return thelengthofthelongest substring of s thatcontainsatmost k distinct characters.  Example......
  • S7.NET读写西门子字符串处理
    S7.NET读写西门子字符串处理由于西门子字符串存储跟C#字符串的存储格式不一样,在与西门子通讯时,在解析/编码字符串时需要特殊处理。在没有看到S7.NET开源库前,一直都在琢磨......