KMP算法

朴素的模式匹配算法

朴素算法就是以主串的每一个字符作为子串的开头,与要匹配的字符串(称为模式串)进行匹配,
匹配失败则主串退回到这次匹配首位的下一位,重新进行匹配。

主串:abcabababcd
          | 
模式串:  ababc

此时匹配失败,那么将跳转到下个首位重新匹配

主串:abcabababcd
       |    
模式串:   ababc

对于主串:00000000000000000000000001与模式串:00000001,查找效率极低,
时间复杂度为O(mn)

KMP算法

kmp算法对于朴素算法的优化在于,每次匹配失败主串的位置不移动,仅仅移动模式串的位置。

主串:abcabababcd
          | 
模式串:  ababc

匹配失败后,仅移动模式串的位置

主串:abcabababcd
          | 
模式串:    ababc

这部分对应的代码为

while (i < len_s && j < len_t) //不要超过最大长度
	{
		if (j == 0 || S[i] == T[j])
		{
			++i;
			++j;
		}//匹配成功继续前进
		else
		{
			j = next[j];//匹配失败模式串跳转到合适位置
		}
	}

这样就节省大量不必要的匹配。
因为这个时候已知模式串前两个字符a,b必定能够匹配

所以问题的关键就在于求解next数组,就是匹配失败后模式串该跳转的位置。
观察模式串:ababc,之所以c匹配失败后能直接跳转到第三个字符(a),是因为abab(没错,不关c的事)这个字符串有相同的真前缀和真后缀(ab)

再举个例子
主串:  zzabcdeeabcdp
                 |     
模式串:   abcdeeabcdq

我们容易看出模式串的最长相同的真前缀和真后缀是abcd,此时p,q匹配失败,
那么我们可以将模式串前面的a,b,c,d与主串后面的a,b,c,d匹配来加速匹配过程,也容易证明跳过的部分一定不能匹配(如果有,那么最长相同真前缀与真后缀将增加),那么接下来匹配的位置就是:

主串:  zzabcdeeabcdp
                 |     
模式串:         abcdeeabcdq

那么现在问题就变成了寻找一个字符串的最长相同的真前缀和真后缀。
这个过程就是模式串自己与自己匹配的过程。

现有模式串abcab,我们来模拟下自己与自己匹配的过程,用len[i],表示前i个字符中最长相同的真前缀和真后缀的长度。
串1:abcab
    |
串2: abcab
首位就不用匹配了,len[1]=0,此时a,b不匹配,那么len[2]=0。
 
串1:abcab
     |
串2:  abcab
不匹配,len[3]=0。
 
串1:abcab
      |
串2:   abcab
成功匹配,那么len[4]=1。 
 
串1:abcab
       |
串2:    abcab
成功,len[5]=2

由于知道了len[i]的大小就是知道了失败后匹配的位置,所以串2匹配时,串2移动的位置可以由len得到,所以模式串自己与自己匹配的过程也使用了kmp。

例题P3375 【模板】KMP字符串匹配
参考代码

//next数组加了优化,ans就是上面的len
//为了方便,用ans[i]表示ans[i-1]的最长相同的真前缀和真后缀
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
char s1[N], s2[N];
#define next _next
int next[N];
int n1, n2;
int ans[N];
void get_next()
{
    int i = 1, k = 0;
    next[i] = 0;
    ans[i] = 0;
    while (i <= n2)
    {
        if (k == 0 || s2[i] == s2[k])
        {//满足这个条件就相当于已经找到了部分相同的真前缀和真后缀
            ++i;++k;
            if (s2[i] != s2[k])
            {
                ans[i] = k - 1;
                next[i] = k;
                k = next[k];
                
            }//若两个字符相等,可直接继承移动的位置
            else next[i] = next[k], ans[i] = k-1;
        }//匹配失败移动到对应位置
        else k = next[k];
    }
}
int main()
{
    scanf("%s", s1 + 1);
    n1 = strlen(s1 + 1);
    scanf("%s", s2 + 1);
    n2 = strlen(s2 + 1);
    s2[n2 + 1] = 'a';//相当于是个结束标志
    n2++;
    get_next();
    if (n2 <= n1 + 1){
        int t = 1; 
        for (int i = 1; i<= n1; ){
            if (t == 0 || s1[i] == s2[t])
            {
                ++i;
                ++t;
                if (t == n2){
                    printf ("%d\n", i - n2 +1);
                    t = next[t];
                    
                }
            }
            else t = next[t];
        }
    }
    for (int i = 2;i <= n2; ++i)
    printf ("%d ", ans[i]);
   return 0;
}
posted @ 2022-09-22 20:10  何太狼  阅读(48)  评论(0)    收藏  举报