ACM算法总结 字符串(一)


KMP

字符串匹配算法,在 O(n+m) 的时间内计算出模式串 s 在文本串 t 中出现的次数。这个算法不仅仅适用于字符串的处理,只要是任何类似的匹配问题都可以使用。

KMP算法的核心思想是在匹配失败时,最大限度地利用之前的匹配信息,以减少需要重复匹配的字符串长度。

在这里插入图片描述

为了运用之前的匹配信息,我们需要知道每个位置匹配失败需要跳转到模式串的哪个位置,也就是我们要构造next数组。

  • next数组定义为:next[i]表示模式串 s 的前缀 {s[1],s[2],…,s[i-1]} 的最长相同前缀后缀的长度加1 ,也就是next[i]指向最佳的下一个匹配位点。

比如说对于字符串 abcdabcefg,其next数组的值为 0 1 1 1 1 2 3 4 1 1

在这里,所有的字符串都从下标1开始存储。

构造模式串next数组的代码如下:

void get_next(char *s,int *nex)
{
    int len=strlen(s+1),i=1,j=0;
    nex[1]=0;
    while(i<=len)
    {
        if(!j || s[i]==s[j]) nex[++i]=++j;
        else j=nex[j];
    }
}

这里构造过程可以结合下图理解:

在这里插入图片描述

i 和 j 实际上记录了当前位置的 next 信息,如果当前位置两个字符相等那么很好理解,下一个位置肯定匹配长度加1;如果不相等的话,实际上,j 又跳往了下一个最佳位置,由于当前 j 的前缀和 i 的一些前缀是匹配的,所以next[j] 的前缀肯定也和 i 的一些更短的前缀匹配(因为next[j] 的前缀和 j 的一些更短的前缀匹配),而且这样跳转可以保证一定是有可能匹配的前后缀中最优的一个,也是最合适的一个(可以反过来想,假设现在s[i]!=s[j],那么对于 i 来说,如果有一个长度比 j-1 短的最长相同前后缀,那么它的长度一定是 next[j]-1)。

有了next数组之后,匹配问题就变得很简单了,相同则继续匹配下一个位置,不同则模式串位点跳往其next值(形象地讲相当于模式串往前移),代码如下:

int KMP(char *s,char *t)
{
    int *nex=new int[maxn];
    get_next(s,nex);
    int lens=strlen(s+1),lent=strlen(t+1),i=1,j=1,ans=0;
    while(i<=lens && j<=lent)
    {
        if(!i || s[i]==t[j]) i++,j++;
        else i=nex[i];
        if(i>lens) {ans++; i=nex[i];}
    }
    return ans;
}

其实从本质上讲,KMP算法也可以理解为一种有限状态机,根据当前状态(模式串匹配位置)和当前输入去决定下一个状态。



扩展KMP

pit……



manacher

又称马拉车算法,这是一种在O(n)时间内求出一个字符串的最长回文子串的算法。

做法是先在原来字符串每两个字符之间插入’#’(或者其它不会出现的字符),前后插入另一个不出现的字符(防止越界),这样就不用分奇偶讨论;然后从前往后计算以s[i](s为处理过后的字符串)为中心的最长回文子串长度2*t[i]-1,计算过程中记录最右扩展位置mx和对应回文子串中心id,每次计算t[i]时,如果i<mx,可以从前面已经计算好的t[2*id-i]开始延伸。

代码如下:

const int maxn=1e6+5;
char s[maxn];
int t[maxn];

int manacher(char *p)
{
    int len=1,maxlen=0,id=0,mx=0;
    s[0]='$';
    for(int i=0;p[i];i++) s[len++]='#',s[len++]=p[i];
    s[len++]='#',s[len]=0;
    REP(i,1,len-1)
    {
        t[i]=i<mx?min(t[2*id-i],mx-i):1;
        while(s[i-t[i]]==s[i+t[i]]) t[i]++;
        if(t[i]+i>mx) mx=t[i]+i,id=i,maxlen=max(maxlen,t[i]);
    }
    return maxlen-1;
}



Trie树

这实际上是一种数据结构,用于高效的存储多个字符串,也称为字典树

就是用一棵树来存储多个字符串信息,从深度浅的结点到深度深的结点的有向边表示字符串的某个字符。

构造方法很简单,注意要开一个数组标记某个结点是否是某个单词的末尾。

const int maxn=5005;
int tree[maxn][26],cnt,e[maxn];

void insert(char *s)
{
    int p=0;
    for(int i=0;s[i];i++)
    {
        int k=s[i]-'a';
        if(!tree[p][k]) tree[p][k]=++cnt;
        p=tree[p][k];
    }
    e[p]++;
}

int query(char *s)
{
    int p=0;
    for(int i=0;s[i];i++)
    {
        int k=s[i]-'a';
        if(!tree[p][k]) return 0;
        p=tree[p][k];
    }
    return e[p];
}

注意这里的Trie树构建过程只有小写字母,如果对于有其它字符的情况,要稍作修改。



AC自动机

网上搜索很多博客都会看到一句经典的话,没错:AC自动机就是Trie树上的KMP

AC自动机用于多模式匹配。类似于KMP中next数组,AC自动机的fail指针(失配指针)指向当前成功匹配的某个模式串前缀的最长后缀。这样可以充分利用已匹配信息。

AC自动机有几个重要性质:

  • AC自动机由Trie树+失配指针组成,本质上是一个有限状态机,每个结点是一个状态;
  • 需要标记单词末尾结点e[i],从头结点(0号结点)开始走到e[i]!=0的结点的路径组成一个模式串;
  • 某个结点的失配指针指向的结点是某个模式串的前缀,而这个前缀是当前结点的后缀;

采用BFS的方式构建fail。对于当前结点u遍历其所有可能的子结点t[u][i](t为Trie树):如果存在t[u][i],那么t[u][i]的失配指针指向u的失配指针的i儿子,即fail[t[u][i]]=t[fail[u]][i];否则,令t[u][i]=t[fail[u]][i]。由于fail[u]的深度一定比u浅,所以在BFS的方式下前面的都已经有了结果,可以直接使用。

代码如下:

const int maxn=1e6+5;
int t[maxn][26],e[maxn],fail[maxn],tot;

void insert(char *s)
{
    int p=0;
    for(int i=0;s[i];i++)
    {
        int k=s[i]-'a';
        if(!t[p][k]) t[p][k]=++tot;
        p=t[p][k];
    }
    e[p]++;
}

void get_fail()
{
    queue<int> Q;
    REP(i,0,25) if(t[0][i]) Q.push(t[0][i]);
    while(!Q.empty())
    {
        int k=Q.front(); Q.pop();
        REP(i,0,25)
        {
            if(t[k][i]) fail[t[k][i]]=t[fail[k]][i],Q.push(t[k][i]);
            else t[k][i]=t[fail[k]][i];
        }
    }
}

int query(char *s)
{
    int p=0,ans=0;
    for(int i=0;s[i];i++)
    {
        int k=s[i]-'a';
        p=t[p][k];
        for(int j=p;j && ~e[j];j=fail[j])
            ans+=e[j],e[j]=-1;		//这里是查询有多少个模式串被匹配
    }
    return ans;
}

fail指针有很多特点,比如说如果把所有fail指针拿出来可以看出,这也组成了一棵树(fail树),当我们要求所有模式串在文本串中出现的次数时,就可以先记录AC自动机上每个状态的出现次数,然后在fail树上面dp就行了(因为当一个状态出现时,说明当前子串匹配成功,那么其fail指向的子串(以及fail的fail等等)也相当于出现了一次(因为是后缀),所以dfs统计一下就可以知道每个状态实际出现次数)。

发布了12 篇原创文章 · 获赞 5 · 访问量 526

猜你喜欢

转载自blog.csdn.net/dragonylee/article/details/103824760
今日推荐