明升体育平台明升体育平台


明升网址

字符串匹配「 KMP 算法 」

引言

众所周知,字符串无论是在 OI 中还是别的计算机领域都占有比较大的比重,今天说的就是一个关于匹配字符串的算法——「 KMP 算法 」。

 

0x00

KMP 算法用于解决这样的一类问题:给定一个文本串 T 和模式串 S,要求你求出 S 在 T 中出现的次数和位置(我们定义位置为 S 中第一个字符在 T 中匹配到的位置)

当然它还有许多别的用法。具体的可以通过一道题目来体现一下 HDU 3336

 

0x01

我们都知道朴素的匹配字符串的算法,不知道的看下面这个例子。

文本串:ABADCADCAB模式串:ADCAB

    A 和 A 匹配,比较下一位。一直到发现 B 和 D 不匹配。不匹配就将模式串后移一位。A 和 B 不匹配,重复之前的过程直到匹配完成。

但是我们发现下面这种情况

ABABCADCAB  ADCAB

最后一位 B 和 D 并不能匹配。但是按照朴素算法的话会将模式串一位一位的往后移动。

直到出现下面的情况

ABABCADCAB     ADCAB

试想,如果在第五位失配后我们可以直接将模式串的第一位直接移动到第四位。

虽然我们能够看出这样很简单,但如何让计算机也看出来呢?

这里我们引入一个 $next$ 数组,通俗的说,$next[i]$ 含义就是长度为 $i$ 的模式串前缀的前缀和后缀所共有的最长的字串。

我们先不说 $next$ 数组怎样求,先来说说它怎么用。

是不是很绕口呢

我们看看这句话是啥意思

模式串:ADCAB前缀:A,AD,ADC,ADCA后缀:B,AB,CAB,DCAB

上面这个就是在一个长度为 $5$ 的前缀中所有的前缀和后缀

我们看这些前后缀中有哪些是满足有共同的元素的

只有下面这两个

AD,AB

它们返回的 $next$ 值是 $1$,也就是说在第五位失配之后,第一位就会跳到第四位去继续匹配。

 

0x02

这个 next 数组怎么求出来的。

我们试图让模式串自己匹配自己。

说到这里大概就知道为什么之前不告诉大家怎么求 $next$ 数组了吧?

因为这个 $next$ 数组会用了就会求了。而两者相比的话,「 用 」这一方面比较简单好理解

看下面这个求 $next$ 数组的代码。

inline void Getnext() { for(int i=2; i<=n; i++) { p = nxt[i-1]; //i的前一位在失配后哪一位会跳过来 while(p && s[p+1] != s[i]) p = nxt[p]; //如果next[i]这一位还是不能和s[i]匹配,那就继续往前跳 //这句话中p!=0表示如果跳到第一位就可以不用跳了 if(s[p+1] == s[i]) nxt[i] = p+1; //如果找到了能够匹配的,就更新next[i] else nxt[i] = 0; //找不到就是0 }}

下面贴一下完整的代码

#include <iostream>#include <cstdio>#include <cstring>const int maxn = 1e6+3;using namespace std;int n, m, next[maxn], p;char T[maxn], S[maxn];inline void Getnext() { for(int i=2; i<=n; i++) { p = next[i-1]; while(p && s[p+1] != s[i]) p = next[p]; if(s[p+1] == s[i]) next[i] = p+1; else next[i] = 0; }}int main() { scanf("%s%s", T+1, S+1); n = strlen(T+1), m = strlen(S+1); Getnx(); p = 0; for(int i=1; i<=n; i++) { while (p && T[i] != S[p+1]) p = next[p]; if(T[i] == S[p+1]) p++; else p = 0; if(p == m) printf("%d", i-m+1), p = next[p]; } for(int i=1; i<=m; i++) { printf("%d ", next[i]); }}

 

欢迎阅读本文章: 马佩玉

m88明升体育网址

明升网址