KMP不成文的见解

(部分分析灵感及样例来源于 @xehoth)
复习模板的时候打了几遍,比当时学的时候感觉要清晰一些,于是抱着KMP在屏幕上滚来滚去滚来滚去滚来滚去~

呐,首先对于几乎所有第一次接触KMP的人来说的那个大坑(喂就是那个神奇的next[ ]数组),我们先按下不表。
假设模式串是 abcieruabc ,目标串是 abc

abcieruabc
---
abc

第一次匹配,匹配成功,如果不用KMP算法

abcieruabc
 |
 abc

第一个就不匹配,若是继续用这种朴素的方法,下一次还是第一个就不匹配。(等等我们换一个更具特色的样例)

ttittittyyios
-----|
ttitty

呐,如果用朴素的方法

ttittittyyios
 -|
 ttitty

再然后

ttittittyyios
  |
  ttitty

。。。。我们还是谈谈KMP好啦。
由于目标串中”tt”重复了两次,则第二次匹配完全可以从第一次第二个”tt”所在的位置开始匹配。
当然,从某一个方面来说’t’也重复了几次,但是

ttittittyyios
    -|
    ttitty

就匹配失败了,所以要从最大重复的字串入手。

放道题来讨论吧。

字符串匹配【KMP模板】

给定两个由小写字母构成的字符串 L 和 S 。
请你从左到右,找出子串 L 在母串 S 中每次出现的开始位置(匹配位置)。

输入格式

第一行:给一个全由小写字母构成的母串 S(0<S的长度≤1000000);
第二行:给一个全由小写字母构成的子串 L(0<L的长度≤S的长度)。

输出格式

按升序输出一行一个整数,分别表示子串 L 在母串 S 中每次出现的开始位置。
如果子串 L 在母串 S 中没有出现,则输出“NO”。

样例数据 1

输入
yuabcierabcde
abc

输出
3
9

样例数据 2

输入
abcdefg
abcdefu

输出
NO

备注

【样例1说明】
从第3个位置起,第一次匹配;
从第9个位置起,第二次匹配。

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

这就是对目标串t的一个初始判定
其实后来二者匹配的时候代码差不多,因为第一个其实可以看做是目标串自己和自己匹配。只是第一次是不要求是否匹配完全,只要求找出是否存在可以匹配的来降低复杂度与时间;第二次就需要讨论一下 j==m 的情况,及是否匹配完全,若完全,将标记 f 进行修改,输出此时最后一个位置i减去目标串的长度m再加1,即此时目标串首位的位置。最终若f作了修改,,则继续寻找下一个匹配点;若没有,输出”NO”,再找下一个匹配点。

bool f=1;
    for(int i=1,j=0;i<=n;i++)
    {
        while(j!=0&&s[i]!=t[j+1])
            j=next[j];
        if(s[i]==t[j+1])
            j++;
        if(j==m)
        {
            cout<<i-m+1<<endl;
            j=next[m],f=0;
        }
    }
    if(f==1)
        cout<<"NO"<<endl;

那么大体上算法如何实现的已经说完了,接下来浅显简短地说一下那个神奇的next[ ]数组。(明明就是自己也不怎么懂吧。。。。人艰不拆穿)
据说是

next 数组就是模式串前缀的最长公共前后缀的长度。

唔那大概是这样的

ttitty      前缀        后缀        next
1 t          -          -           0
2 tt         t          t           1
3 tti       t,tt       ti,i         0
4 ttit    t,tt,tti   tit it t       1
5 ttitt     ……        ……         ……
6 ttitty    ……        ……         ……

(部分分析灵感及样例来源于 @xehoth)

说了那么多,还有完整版无水代码。

#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<iomanip>
using namespace std;

int n,m;
int next[1000010];
char s[1000010],t[1000010];

int main()
{
    scanf("%s%s",s+1,t+1);
    n=strlen(s+1),m=strlen(t+1);
    for(int i=2,j=0;i<=m;i++)
    {
        while(j!=0&&t[i]!=t[j+1])
            j=next[j];
        if(t[i]==t[j+1])
            j++;
        next[i]=j;
    }
    bool f=1;
    for(int i=1,j=0;i<=n;i++)
    {
        while(j!=0&&s[i]!=t[j+1])
            j=next[j];
        if(s[i]==t[j+1])
            j++;
        if(j==m)
        {
            cout<<i-m+1<<endl;
            j=next[m],f=0;
        }
    }
    if(f==1)
        cout<<"NO"<<endl;
    return 0;
}

喵呜~

(部分分析灵感及样例来源于 @xehoth)
来自2017.10.10

——我认为return 0,是一个时代的终结。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章