芝士:KMP

kmp

引例

传送门

过程

考虑普通的暴力的过程,

其复杂度主要是卡在模式串只向右边移动一个位置

很明显,这忽略了串内部的联系

就比如模式串\(aaabaaac\),如果在比较c的时候出现了错误,真的需要从头开始比较么?

很显然,可以从\(b\)开始进行再一次的比较

那么可以考虑求出以\(i\)为尾端点的后缀所能匹配到的最长前缀

就比如上一个模式串中的\(nxt[7]=3\)(字符串从1开始)

如果已经求出了\(nxt\)数组,就可以利用上面所说的过程进行匹配

那么现在的问题就在于怎么求出\(nxt\)数组

如果\(t[nxt[i-1]+1]==t[i]\),那么就直接有\(nxt[i]=nxt[i-1]+1\)

如果没有,也是考虑用匹配的思路去进行计算,即我可以考虑\(t[nxt[nxt[i-1]]+1]==t[i]\)

很明显,用反证法可以说明这一定是最长的前缀

就比如$abacabab $

很明显有\(nxt[7]=3\)

但是发现\(t[4]!=t[8]\)

故用\(nxt[3]+1\)来匹配\(t[8]\)

因为前缀和后缀是一样的,所以算法的正确性也不难证明

时间复杂度

求nxt

考虑总共的转移过程,第一次一定是从\(nxt[i-1]\)开始

考虑如果要\(nxt[i]\)变大,那么此时只会提供\(O(1)\),即只会在前一个+1

考虑\(nxt[i]\)变小,那么一定是在\(nxt[i-1]\)上进行操作

可以看作拔高和降低操作,一定是有高度才能降低

故总的时间复杂度为\(O(n)\)

匹配

考虑模式串在文本串中的已经匹配的区间为\([l,r]\)(文本串中的区间)

如果下一个字符可以进行匹配,那么\(r\)就会往右移动,\(l\)不动

如果下一个字符不能进行匹配,那么\(l\)就会往左移动,\(r\)不动

考虑每一次动都至少会动1个单位

所以时间复杂度为\(O(n)\)

代码

#include<iostream>
#include<cstring>
using namespace std;
char t[1000005];//文本串
char s[1000005];//模式串
int nxt[1000005];//失配数组
int lens,lent;
int main()
{
    cin>>(t+1)>>(s+1);
    lent=strlen(t+1);
    lens=strlen(s+1);
    int j=0;
    for(int i=1;i<=lens;i++)
    {
        while(s[j+1]!=s[i]&&j)
            j=nxt[j];
        if(s[j+1]==s[i]&&i!=j+1)
            j++;
        nxt[i]=j;
    }   
    j=0;
    for(int i=1;i<=lent;i++)
    {
        while(t[i]!=s[j+1]&&j!=0)
            j=nxt[j];
        if(t[i]==s[j+1])
            j++;
        if(j==lens)
        {
            cout<<i-lens+1<<'\n';

    		j=nxt[j];
		}
	}
    for(int i=1;i<=lens;i++)
        cout<<nxt[i]<<' ';
    return 0;
}

exkmp

咕咕咕

posted @ 2020-12-08 22:19  loney_s  阅读(141)  评论(0)    收藏  举报